diff --git a/etc/eslint/eslint.flat.config.markdown.js b/etc/eslint/eslint.flat.config.markdown.js new file mode 100644 index 000000000000..00989dcd5d65 --- /dev/null +++ b/etc/eslint/eslint.flat.config.markdown.js @@ -0,0 +1,132 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2026 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var assign = require( './../../lib/node_modules/@stdlib/object/assign' ); +var defaults = require( './../../eslint.flat.config.js' ); + + +// VARIABLES // + +var config; +var entry; +var i; + + +// MAIN // + +/** +* ESLint flat configuration for linting Markdown code blocks. +*/ +config = [ + // Allow linting code blocks belonging to files residing under `node_modules` directories (e.g., package files residing under `lib/node_modules`), which ESLint unconditionally ignores by default: + { + 'ignores': [ '!**/node_modules/' ] + } +]; + +// Include the default configuration, ensuring that Markdown files are matched, as code blocks are linted using the path of the containing Markdown file: +for ( i = 0; i < defaults.length; i++ ) { + entry = defaults[ i ]; + if ( entry.files && entry.files.indexOf( '**/*.js' ) !== -1 ) { + entry = assign( {}, entry ); + entry.files = entry.files.concat( [ '**/*.md' ] ); + } + config.push( entry ); +} + +// Append configuration specific to Markdown code blocks: +config.push({ + 'linterOptions': { + // Do not flag unused disable directives, as the remark plugin unconditionally injects a `stdlib/require-order` disable directive when prepending the main `require` statement to subsequent code blocks: + 'reportUnusedDisableDirectives': 'off' + }, + 'rules': { + // Allow variables to be declared as needed: + 'vars-on-top': 'off', + + // Allow using synchronous methods: + 'n/no-sync': 'off', + + // Allow using `console`: + 'no-console': 'off', + + // Do not require `use strict` pragma: + 'strict': 'off', + + // Do not require an end-of-line character in code blocks: + 'eol-last': 'off', + + // Require `4` space indentation: + 'indent': [ 'error', 4, { + 'SwitchCase': 0, + 'VariableDeclarator': 1, + 'outerIIFEBody': 1, + 'MemberExpression': 1, + 'FunctionDeclaration': { + 'body': 1, + 'parameters': 'off' + }, + 'FunctionExpression': { + 'body': 1, + 'parameters': 'off' + }, + 'CallExpression': { + 'arguments': 'off' + }, + 'ArrayExpression': 1, + 'ObjectExpression': 1, + 'flatTernaryExpressions': true + }], + + // Never allow tabs: + 'no-tabs': 'error', + + // Do not require JSDoc comments: + 'jsdoc/require-jsdoc': 'off', + + // Do not require `@private` annotations: + 'stdlib/jsdoc-private-annotation': 'off', + + // Do not lint return annotation values in JSDoc comments (FIXME: remove this once we can reliably lint Markdown code blocks): + 'stdlib/jsdoc-return-annotations-values': 'off', + + // Do not enforce disallowing empty lines between module-level require statements: + 'stdlib/no-empty-lines-between-requires': 'off', + + // Allow requiring the package as a whole even though only a single property is used: + 'stdlib/no-single-property-require': 'off', + + // Allow use of undeclared variables, as variables may be defined in previous code blocks or be implied: + 'no-undef': 'off', + + // Allow unused variables, as variables may be illustrative or used in subsequent code blocks: + 'no-unused-vars': 'off', + + // Allow unpublished packages to be required in example code: + 'n/no-unpublished-require': 'off' + } +}); + + +// EXPORTS // + +module.exports = config; diff --git a/etc/remark/plugins/eslint/index.js b/etc/remark/plugins/eslint/index.js index 0f3e2d5e75b9..eede4d548f3b 100644 --- a/etc/remark/plugins/eslint/index.js +++ b/etc/remark/plugins/eslint/index.js @@ -27,15 +27,13 @@ var join = require( 'path' ).join; // VARIABLES // var etc = resolve( __dirname, '..', '..', '..' ); -var config = join( etc, 'eslint', '.eslintrc.markdown.js' ); +var config = join( etc, 'eslint', 'eslint.flat.config.markdown.js' ); var eslint = resolve( etc, '..', 'lib', 'node_modules', '@stdlib', '_tools', 'remark', 'plugins', 'remark-lint-eslint' ); var opts = { 'config': config, 'rules': { 'stdlib/doctest': 'error' // enable otherwise disabled lint rule - }, - 'ignore': false, - 'useEslintrc': false + } }; diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/doctest/lib/main.js b/lib/node_modules/@stdlib/_tools/eslint/rules/doctest/lib/main.js index 8fab198588a2..1877ed87a3ef 100644 --- a/lib/node_modules/@stdlib/_tools/eslint/rules/doctest/lib/main.js +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/doctest/lib/main.js @@ -26,6 +26,7 @@ var join = require( 'path' ).join; var format = require( 'util' ).format; var dirname = require( 'path' ).dirname; var logger = require( 'debug' ); +var extname = require( '@stdlib/utils/extname' ); var rootDir = require( '@stdlib/_tools/utils/root-dir' ); var Buffer = require( '@stdlib/buffer/ctor' ); var isNull = require( '@stdlib/assert/is-null' ); @@ -162,6 +163,15 @@ function main( context ) { 'clearInterval': clearInterval, 'window': windowShim, 'Buffer': Buffer, + + // Share the host realm's error constructors, as required modules are loaded in the host realm, meaning that, without sharing, `instanceof` checks on host-created errors would always fail within the sandbox: + 'Error': Error, + 'EvalError': EvalError, + 'RangeError': RangeError, + 'ReferenceError': ReferenceError, + 'SyntaxError': SyntaxError, + 'TypeError': TypeError, + 'URIError': URIError, '__dirname': dir, '__filename': filename, 'console': { @@ -327,7 +337,7 @@ function main( context ) { }; // Do not report errors in `Markdown` fenced code blocks due to modules failing to load as implementations may not be available yet (readme-driven development): - if ( filename === '' && contains( err.message, 'Cannot find module' ) ) { + if ( ( filename === '' || extname( filename ) === '.md' ) && contains( err.message, 'Cannot find module' ) ) { return; } report( loc, 'Encountered an error while running code: '+err.message ); diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/doctest/lib/scope_defaults.json b/lib/node_modules/@stdlib/_tools/eslint/rules/doctest/lib/scope_defaults.json index fab6c5ea05dc..0a0ba4ec3f5f 100644 --- a/lib/node_modules/@stdlib/_tools/eslint/rules/doctest/lib/scope_defaults.json +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/doctest/lib/scope_defaults.json @@ -9,6 +9,13 @@ "clearInterval", "window", "Buffer", + "Error", + "EvalError", + "RangeError", + "ReferenceError", + "SyntaxError", + "TypeError", + "URIError", "__dirname", "__filename", "console" diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/require-file-extensions/lib/main.js b/lib/node_modules/@stdlib/_tools/eslint/rules/require-file-extensions/lib/main.js index e7af4d6eb31a..90914ca7f2cf 100644 --- a/lib/node_modules/@stdlib/_tools/eslint/rules/require-file-extensions/lib/main.js +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/require-file-extensions/lib/main.js @@ -27,6 +27,7 @@ var hasOwnProp = require( '@stdlib/assert/has-own-property' ); var isString = require( '@stdlib/assert/is-string' ); var contains = require( '@stdlib/assert/contains' ); var dirname = require( '@stdlib/utils/dirname' ); +var extname = require( '@stdlib/utils/extname' ); var DELIMITER = require( '@stdlib/constants/path/delimiter' ); var ENV = require( '@stdlib/process/env' ); @@ -139,7 +140,7 @@ function main( context ) { return; } // Ignore error for internal requires in fenced code blocks of Markdown files as the package's implementation may not yet be available... - if ( filename === '' && startsWith( requirePath, '@stdlib' ) ) { + if ( ( filename === '' || extname( filename ) === '.md' ) && startsWith( requirePath, '@stdlib' ) ) { return; } return report( node, 'cannot resolve module: "'+requirePath+'"' ); diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-eslint/README.md b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-eslint/README.md index c686835933dc..360fb7a92082 100644 --- a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-eslint/README.md +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-eslint/README.md @@ -60,7 +60,7 @@ var vfile = linter( '```javascript\nvar beep = \'boop\';\n```' ); The function recognizes the following `options`: -- **config**: path to an [ESLint][eslint] configuration file. A configuration path is resolved relative to the current working directory of the calling process. +- **config**: path to an [ESLint][eslint] "flat" configuration file. A configuration path is resolved relative to the current working directory of the calling process. To specify configuration `options`, set the respective properties. @@ -71,7 +71,7 @@ var remark = require( 'remark' ); // Define options: var opts = { - 'config': '/path/to/.eslintrc' + 'config': '/path/to/eslint.config.js' }; // Create a plugin: @@ -196,9 +196,11 @@ var vfile = linter( '```javascript\nvar beep = \'boop\';\n```' ); ## Examples - + - + + + ```javascript var join = require( 'path' ).join; @@ -208,7 +210,7 @@ var readFileSync = require( '@stdlib/fs/read-file' ).sync; var factory = require( '@stdlib/_tools/remark/plugins/remark-lint-eslint' ).factory; // Define path to an ESLint config file: -var config = resolve( __dirname, '..', '..', '..', '..', '..', '..', '..', 'etc', 'eslint', '.eslintrc.markdown.js' ); +var config = resolve( __dirname, '..', '..', '..', '..', '..', '..', '..', 'etc', 'eslint', 'eslint.flat.config.markdown.js' ); // Load a Markdown file: var fpath = join( __dirname, 'examples', 'fixtures', 'file.md' ); @@ -220,10 +222,10 @@ var opts = { }; // Create a plugin: -var plugin = factory( opts ); +var lint = factory( opts ); // Lint code blocks: -var out = remark().use( plugin ).processSync( file.toString() ); +var out = remark().use( lint ).processSync( file.toString() ); console.log( out ); ``` diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-eslint/examples/index.js b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-eslint/examples/index.js index 64e696806cd8..88a4c565dec9 100644 --- a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-eslint/examples/index.js +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-eslint/examples/index.js @@ -16,6 +16,10 @@ * limitations under the License. */ +/* eslint stdlib/doctest: "off" */ + +// Note: executing this example via the `stdlib/doctest` rule would run the linter within the linter and deadlock; hence, the rule is disabled for this file. + 'use strict'; var join = require( 'path' ).join; @@ -25,7 +29,7 @@ var readFileSync = require( '@stdlib/fs/read-file' ).sync; var factory = require( './../lib' ).factory; // Define path to an ESLint config file: -var config = resolve( __dirname, '..', '..', '..', '..', '..', '..', '..', '..', 'etc', 'eslint', '.eslintrc.markdown.js' ); +var config = resolve( __dirname, '..', '..', '..', '..', '..', '..', '..', '..', 'etc', 'eslint', 'eslint.flat.config.markdown.js' ); // Load a Markdown file: var fpath = join( __dirname, 'fixtures', 'file.md' ); diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-eslint/lib/factory.js b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-eslint/lib/factory.js index f5d3fa2b6b4b..bc58d7ae577d 100644 --- a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-eslint/lib/factory.js +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-eslint/lib/factory.js @@ -33,6 +33,14 @@ var format = require( '@stdlib/string/format' ); var transformHTML = require( './transform_html.js' ); +// VARIABLES // + +// Resolve an ESLint class supporting "flat" configuration (note: in ESLint v9+, the default `ESLint` class supports "flat" configuration; in ESLint v8, a separate class must be explicitly loaded): +if ( parseInt( ESLint.version, 10 ) < 9 ) { + ESLint = require( 'eslint/use-at-your-own-risk' ).FlatESLint; // eslint-disable-line stdlib/require-file-extensions, stdlib/require-order +} + + // MAIN // /** @@ -70,9 +78,6 @@ function factory( options ) { if ( hasOwnProp( options, 'ignore' ) ) { opts.ignore = options.ignore; } - if ( hasOwnProp( options, 'useEslintrc' ) ) { - opts.useEslintrc = options.useEslintrc; - } // TODO: add support for other ESLint options } eslint = new ESLint( opts ); @@ -89,6 +94,14 @@ function factory( options ) { */ function lint( tree, file ) { var FIRST; + var fpath; + + // Resolve the path of the file being linted, which allows linting code blocks in the context of the containing file (e.g., resolving relative require paths and providing meaningful `__filename` and `__dirname` values when evaluating doctests): + if ( file.path ) { + fpath = resolve( cwd(), file.path ); + } else { + fpath = null; + } visit( tree, 'code', onNode ); /** @@ -152,7 +165,13 @@ function factory( options ) { code = comments.join( '\n' ); // Lint the code block... - awaitSync( eslint.lintText( code ).then( onLint ) ); + if ( fpath ) { + awaitSync( eslint.lintText( code, { + 'filePath': fpath + }).then( onLint ) ); + } else { + awaitSync( eslint.lintText( code ).then( onLint ) ); + } } /**