From 9dbe8eb0a0fea935a1a54f6dac1b7a1f59d79e8c Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Mon, 23 Mar 2026 00:41:03 -0500 Subject: [PATCH] feat: add `jsdoc-example-eslint` ESLint rule Add a new custom ESLint rule that extracts JavaScript code from JSDoc `@example` blocks and lints it using ESLint's synchronous `Linter` API. The rule is a pure mechanism: it accepts a `rules` object and lints example code with those rules, reporting violations mapped back to the correct source file lines. The rule is configured in `.eslintrc.overrides.js`, where the examples ESLint config is loaded (avoiding a circular dependency with `stdlib.js`), filtered to exclude plugin rules and rules incompatible with code snippets, and passed to the rule. --- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown status: passed - task: lint_package_json status: passed - task: lint_repl_help status: na - task: lint_javascript_src status: passed - task: lint_javascript_cli status: na - task: lint_javascript_examples status: passed - task: lint_javascript_tests status: passed - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: na - task: lint_typescript_declarations status: passed - task: lint_typescript_tests status: na - task: lint_license_headers status: passed --- --- etc/eslint/.eslintrc.overrides.js | 66 +++++- etc/eslint/rules/stdlib.js | 32 +++ .../rules/jsdoc-example-eslint/README.md | 152 ++++++++++++++ .../jsdoc-example-eslint/examples/index.js | 62 ++++++ .../rules/jsdoc-example-eslint/lib/index.js | 43 ++++ .../rules/jsdoc-example-eslint/lib/main.js | 178 ++++++++++++++++ .../rules/jsdoc-example-eslint/package.json | 66 ++++++ .../test/fixtures/invalid.js | 85 ++++++++ .../test/fixtures/valid.js | 191 ++++++++++++++++++ .../rules/jsdoc-example-eslint/test/test.js | 70 +++++++ .../@stdlib/_tools/eslint/rules/lib/index.js | 9 + tools/make/lib/init/eslint.mk | 1 + 12 files changed, 954 insertions(+), 1 deletion(-) create mode 100644 lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/README.md create mode 100644 lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/examples/index.js create mode 100644 lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/lib/index.js create mode 100644 lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/lib/main.js create mode 100644 lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/package.json create mode 100644 lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/test/fixtures/invalid.js create mode 100644 lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/test/fixtures/valid.js create mode 100644 lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/test/test.js diff --git a/etc/eslint/.eslintrc.overrides.js b/etc/eslint/.eslintrc.overrides.js index 8b603bf87967..2ac35dfe0dff 100644 --- a/etc/eslint/.eslintrc.overrides.js +++ b/etc/eslint/.eslintrc.overrides.js @@ -16,15 +16,72 @@ * limitations under the License. */ +/* eslint-disable vars-on-top, stdlib/empty-line-before-comment */ + 'use strict'; // MODULES // // FIXME: remove the next line and uncomment the subsequent line once all remark JSDoc ESLint rules are completed var copy = require( './../../lib/node_modules/@stdlib/utils/copy' ); - // var copy = require( './utils/copy.js' ); +var objectKeys = require( './../../lib/node_modules/@stdlib/utils/keys' ); var defaults = require( './.eslintrc.js' ); +var examplesConfig = require( './.eslintrc.examples.js' ); + + +// VARIABLES // + +/** +* List of rules to exclude when linting JSDoc code snippets because they are incompatible with code fragments extracted from JSDoc comments. +* +* @private +* @type {Array} +*/ +var JSDOC_SNIPPET_EXCLUDE = [ + 'no-undef', + 'no-unused-vars', + 'strict', + 'no-var', + 'eol-last', + 'indent', + 'no-restricted-syntax' +]; + + +// FUNCTIONS // + +/** +* Extracts built-in ESLint rules from a configuration object, filtering out plugin rules and rules listed in an exclusion list. +* +* @private +* @param {Object} config - ESLint configuration object +* @param {Array} exclude - rule names to exclude +* @returns {Object} filtered rules object +*/ +function extractSnippetRules( config, exclude ) { + var rules; + var keys; + var key; + var i; + + rules = {}; + keys = objectKeys( config.rules ); + for ( i = 0; i < keys.length; i++ ) { + key = keys[ i ]; + + // Skip plugin rules (e.g., "stdlib/foo", "node/bar") since the Linter instance does not have them registered: + if ( key.indexOf( '/' ) !== -1 ) { + continue; + } + // Skip rules explicitly excluded as incompatible with code snippets: + if ( exclude.indexOf( key ) !== -1 ) { + continue; + } + rules[ key ] = config.rules[ key ]; + } + return rules; +} // MAIN // @@ -45,6 +102,13 @@ var eslint = copy( defaults ); */ eslint.overrides = require( './overrides' ); +/** +* Configure JSDoc example linting using the examples ESLint config, with rules filtered for use on JSDoc code snippets. +*/ +eslint.rules[ 'stdlib/jsdoc-example-eslint' ] = [ 'warn', { + 'rules': extractSnippetRules( examplesConfig, JSDOC_SNIPPET_EXCLUDE ) +}]; + // EXPORTS // diff --git a/etc/eslint/rules/stdlib.js b/etc/eslint/rules/stdlib.js index cfc51e8e65b8..05caeda6bf91 100644 --- a/etc/eslint/rules/stdlib.js +++ b/etc/eslint/rules/stdlib.js @@ -867,6 +867,38 @@ rules[ 'stdlib/jsdoc-emphasis-marker' ] = [ 'error', '_' ]; */ rules[ 'stdlib/jsdoc-empty-line-before-example' ] = 'error'; +/** +* Lint JavaScript code in JSDoc example blocks using ESLint. +* +* @name jsdoc-example-eslint +* @memberof rules +* @type {string} +* @default 'warn' +* +* @example +* // Bad... +* +* /** +* * Squares a number. +* * +* * @example +* * var y = square( 3.0 ) +* *\/ +* function square( x ) {} +* +* @example +* // Good... +* +* /** +* * Squares a number. +* * +* * @example +* * var y = square( 3.0 ); +* *\/ +* function square( x ) {} +*/ +rules[ 'stdlib/jsdoc-example-eslint' ] = 'off'; + /** * Enforce empty lines between requires and code in JSDoc examples. * diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/README.md b/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/README.md new file mode 100644 index 000000000000..8e0caed7c3c7 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/README.md @@ -0,0 +1,152 @@ + + +# jsdoc-example-eslint + +> [ESLint rule][eslint-rules] to lint JavaScript code in JSDoc example blocks. + +
+ +
+ + + +
+ +## Usage + +```javascript +var rule = require( '@stdlib/_tools/eslint/rules/jsdoc-example-eslint' ); +``` + +#### rule + +[ESLint rule][eslint-rules] to lint JavaScript code in JSDoc example blocks. The rule extracts code from `@example` tags in JSDoc comments, lints it using a provided set of ESLint rules, and reports violations mapped back to the original source lines. + +The rule accepts an options object with a `rules` property specifying which ESLint rules to apply to the extracted example code. + +**Bad** (missing semicolon): + + + +```javascript +/** +* Squares a number. +* +* @example +* var y = square( 3.0 ) +*/ +function square( x ) { + return x * x; +} +``` + +**Good**: + + + +```javascript +/** +* Squares a number. +* +* @example +* var y = square( 3.0 ); +*/ +function square( x ) { + return x * x; +} +``` + +
+ + + +
+ +## Examples + + + +```javascript +var Linter = require( 'eslint' ).Linter; +var rule = require( '@stdlib/_tools/eslint/rules/jsdoc-example-eslint' ); + +var linter = new Linter(); +var result; +var code; + +code = [ + '/**', + '* Squares a number.', + '*', + '* @example', + '* var y = square( 3.0 )', + '* // returns 9.0', + '*/', + 'function square( x ) {', + '\treturn x * x;', + '}' +].join( '\n' ); + +linter.defineRule( 'jsdoc-example-eslint', rule ); + +result = linter.verify( code, { + 'rules': { + 'jsdoc-example-eslint': [ 'error', { + 'rules': { + 'semi': 'error' + } + }] + } +}); +console.log( result ); +/* => + [ + { + 'ruleId': 'jsdoc-example-eslint', + 'severity': 2, + 'message': 'Missing semicolon. (semi)', + ... + } + ] +*/ +``` + +
+ + + + + + + + + + + + + + diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/examples/index.js b/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/examples/index.js new file mode 100644 index 000000000000..3cd7996bd321 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/examples/index.js @@ -0,0 +1,62 @@ +/** +* @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'; + +var Linter = require( 'eslint' ).Linter; +var rule = require( './../lib' ); + +var linter = new Linter(); +var result; +var code; + +code = [ + '/**', + '* Squares a number.', + '*', + '* @example', + '* var y = square( 3.0 )', + '* // returns 9.0', + '*/', + 'function square( x ) {', + '\treturn x * x;', + '}' +].join( '\n' ); + +linter.defineRule( 'jsdoc-example-eslint', rule ); + +result = linter.verify( code, { + 'rules': { + 'jsdoc-example-eslint': [ 'error', { + 'rules': { + 'semi': 'error' + } + }] + } +}); +console.log( result ); +/* => + [ + { + 'ruleId': 'jsdoc-example-eslint', + 'severity': 2, + 'message': 'Missing semicolon. (semi)', + ... + } + ] +*/ diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/lib/index.js b/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/lib/index.js new file mode 100644 index 000000000000..0446247c07af --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/lib/index.js @@ -0,0 +1,43 @@ +/** +* @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'; + +/** +* ESLint rule to lint JavaScript code in JSDoc example blocks. +* +* @module @stdlib/_tools/eslint/rules/jsdoc-example-eslint +* +* @example +* var rule = require( '@stdlib/_tools/eslint/rules/jsdoc-example-eslint' ); +* +* var config = { +* 'rules': { +* 'stdlib/jsdoc-example-eslint': 'warn' +* } +* }; +*/ + +// MODULES // + +var rule = require( './main.js' ); + + +// EXPORTS // + +module.exports = rule; diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/lib/main.js b/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/lib/main.js new file mode 100644 index 000000000000..de0b06f67a97 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/lib/main.js @@ -0,0 +1,178 @@ +/** +* @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 Linter = require( 'eslint' ).Linter; +var parseJSDoc = require( 'doctrine' ).parse; +var isObject = require( '@stdlib/assert/is-object' ); +var objectKeys = require( '@stdlib/utils/keys' ); +var contains = require( '@stdlib/assert/contains' ); +var replace = require( '@stdlib/string/replace' ); +var format = require( '@stdlib/string/format' ); +var findJSDoc = require( '@stdlib/_tools/eslint/utils/find-jsdoc' ); + + +// VARIABLES // + +var RE_MODULE_TAG = /\* @module[^\n]*/g; +var DOPTS = { + 'sloppy': true, + 'unwrap': true, + 'tags': [ 'example' ], + 'lineNumbers': true +}; +var linter = new Linter(); +var rule; + + +// FUNCTIONS // + +/** +* Rule for linting JavaScript code in JSDoc example blocks using ESLint. +* +* @param {Object} context - ESLint context +* @returns {Object} validators +*/ +function main( context ) { + var source; + var config; + var opts; + + source = context.sourceCode; + + // Build linter configuration from the provided rules: + opts = context.options[ 0 ]; + if ( !isObject( opts ) || !isObject( opts.rules ) || objectKeys( opts.rules ).length === 0 ) { + return {}; + } + config = { + 'rules': opts.rules + }; + + /** + * Checks JavaScript code in JSDoc example blocks for lint violations. + * + * @private + * @param {ASTNode} node - AST node + */ + function validate( node ) { + var wrapped; + var jsdoc; + var tags; + var tag; + var ast; + var j; + + if ( node.parent.type !== 'Program' ) { + return; + } + jsdoc = findJSDoc( source, node ); + if ( isObject( jsdoc ) ) { + if ( contains( jsdoc.value, '@module' ) ) { + wrapped = '/**' + jsdoc.value + '*/'; + wrapped = replace( wrapped, RE_MODULE_TAG, '*' ); + ast = parseJSDoc( wrapped, DOPTS ); + } else { + ast = parseJSDoc( jsdoc.value, DOPTS ); + } + tags = ast.tags; + for ( j = 0; j < tags.length; j++ ) { + tag = tags[ j ]; + if ( tag.title === 'example' && tag.description ) { + lintExample( jsdoc, tag ); + } + } + } + } + + /** + * Lints an example code block and reports violations. + * + * @private + * @param {Object} jsdoc - JSDoc comment token + * @param {Object} tag - doctrine tag object with description and lineNumber + */ + function lintExample( jsdoc, tag ) { + var violations; + var sourceLine; + var msg; + var v; + var i; + + violations = linter.verify( tag.description, config ); + for ( i = 0; i < violations.length; i++ ) { + v = violations[ i ]; + + // Skip fatal parse errors (syntax errors in example code): + if ( v.fatal ) { + continue; + } + sourceLine = jsdoc.loc.start.line + tag.lineNumber + v.line; + msg = format( '%s (%s)', v.message, v.ruleId ); + context.report({ + 'node': null, + 'loc': { + 'start': { + 'line': sourceLine, + 'column': 0 + } + }, + 'message': msg + }); + } + } + + return { + 'FunctionDeclaration': validate, + 'FunctionExpression:exit': validate, + 'VariableDeclaration': validate, + 'ExpressionStatement': validate + }; +} + + +// MAIN // + +rule = { + 'meta': { + 'type': 'suggestion', + 'docs': { + 'description': 'lint JavaScript code in JSDoc example blocks using ESLint' + }, + 'schema': [ + { + 'type': 'object', + 'properties': { + 'rules': { + 'type': 'object' + } + }, + 'additionalProperties': false + } + ] + }, + 'create': main +}; + + +// EXPORTS // + +module.exports = rule; diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/package.json b/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/package.json new file mode 100644 index 000000000000..5a1dd26a1f90 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/package.json @@ -0,0 +1,66 @@ +{ + "name": "@stdlib/_tools/eslint/rules/jsdoc-example-eslint", + "version": "0.0.0", + "description": "ESLint rule to lint JavaScript code in JSDoc example blocks.", + "license": "Apache-2.0", + "author": { + "name": "The Stdlib Authors", + "url": "https://github.com/stdlib-js/stdlib/graphs/contributors" + }, + "contributors": [ + { + "name": "The Stdlib Authors", + "url": "https://github.com/stdlib-js/stdlib/graphs/contributors" + } + ], + "bin": {}, + "main": "./lib", + "directories": { + "example": "./examples", + "lib": "./lib", + "test": "./test" + }, + "scripts": {}, + "homepage": "https://github.com/stdlib-js/stdlib", + "repository": { + "type": "git", + "url": "git://github.com/stdlib-js/stdlib.git" + }, + "bugs": { + "url": "https://github.com/stdlib-js/stdlib/issues" + }, + "dependencies": {}, + "devDependencies": {}, + "engines": { + "node": ">=0.10.0", + "npm": ">2.7.0" + }, + "os": [ + "aix", + "darwin", + "freebsd", + "linux", + "macos", + "openbsd", + "sunos", + "win32", + "windows" + ], + "keywords": [ + "stdlib", + "tools", + "tool", + "eslint", + "lint", + "custom", + "rules", + "rule", + "plugin", + "jsdoc", + "examples", + "example", + "style", + "code", + "quality" + ] +} diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/test/fixtures/invalid.js b/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/test/fixtures/invalid.js new file mode 100644 index 000000000000..d65f2ba3b42f --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/test/fixtures/invalid.js @@ -0,0 +1,85 @@ +/** +* @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'; + +var invalid = []; +var test; + +// Missing semicolon: +test = { + 'code': [ + '/**', + '* Squares a number.', + '*', + '* @example', + '* var y = square( 3.0 )', + '* // returns 9.0', + '*/', + 'function square( x ) {', + '\treturn x * x;', + '}' + ].join( '\n' ), + 'options': [ + { + 'rules': { + 'semi': 'error' + } + } + ], + 'errors': [ + { + 'message': 'Missing semicolon. (semi)' + } + ] +}; +invalid.push( test ); + +// Use of == instead of ===: +test = { + 'code': [ + '/**', + '* Checks equality.', + '*', + '* @example', + '* var bool = ( 1 == \'1\' );', + '* // returns true', + '*/', + 'function isEqual( a, b ) {', + '\treturn a === b;', + '}' + ].join( '\n' ), + 'options': [ + { + 'rules': { + 'eqeqeq': 'error' + } + } + ], + 'errors': [ + { + 'message': 'Expected \'===\' and instead saw \'==\'. (eqeqeq)' + } + ] +}; +invalid.push( test ); + + +// EXPORTS // + +module.exports = invalid; diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/test/fixtures/valid.js b/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/test/fixtures/valid.js new file mode 100644 index 000000000000..a8d0621109dc --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/test/fixtures/valid.js @@ -0,0 +1,191 @@ +/** +* @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'; + +// VARIABLES // + +var OPTS = [ + { + 'rules': { + 'semi': 'error', + 'eqeqeq': 'error' + } + } +]; + +var valid = []; +var test; + +// Clean example with proper style: +test = { + 'code': [ + '/**', + '* Squares a number.', + '*', + '* @example', + '* var y = square( 3.0 );', + '* // returns 9.0', + '*/', + 'function square( x ) {', + '\treturn x * x;', + '}' + ].join( '\n' ), + 'options': OPTS +}; +valid.push( test ); + +// JSDoc without @example tag: +test = { + 'code': [ + '/**', + '* Squares a number.', + '*', + '* @param {number} x - input value', + '* @returns {number} squared value', + '*/', + 'function square( x ) {', + '\treturn x * x;', + '}' + ].join( '\n' ), + 'options': OPTS +}; +valid.push( test ); + +// Multiple clean examples: +test = { + 'code': [ + '/**', + '* Computes an absolute value.', + '*', + '* @example', + '* var v = abs( -1.0 );', + '* // returns 1.0', + '*', + '* @example', + '* var v = abs( 2.0 );', + '* // returns 2.0', + '*/', + 'function abs( x ) {', + '\tif ( x < 0 ) {', + '\t\treturn -x;', + '\t}', + '\treturn x;', + '}' + ].join( '\n' ), + 'options': OPTS +}; +valid.push( test ); + +// Example with require statement: +test = { + 'code': [ + '/**', + '* Computes an absolute value.', + '*', + '* @module @stdlib/math/base/special/abs', + '*', + '* @example', + '* var abs = require( \'@stdlib/math/base/special/abs\' );', + '*', + '* var v = abs( -1.0 );', + '* // returns 1.0', + '*/', + 'var main = require( \'./main.js\' );', + '', + 'module.exports = main;' + ].join( '\n' ), + 'options': OPTS +}; +valid.push( test ); + +// Example with console.log: +test = { + 'code': [ + '/**', + '* Squares a number.', + '*', + '* @example', + '* var y = square( 3.0 );', + '* console.log( y );', + '*/', + 'function square( x ) {', + '\treturn x * x;', + '}' + ].join( '\n' ), + 'options': OPTS +}; +valid.push( test ); + +// Variable declaration with example: +test = { + 'code': [ + '/**', + '* Maximum value.', + '*', + '* @example', + '* var v = MAX;', + '* // returns 100', + '*/', + 'var MAX = 100;' + ].join( '\n' ), + 'options': OPTS +}; +valid.push( test ); + +// Example with if statement and curly braces: +test = { + 'code': [ + '/**', + '* Checks if positive.', + '*', + '* @example', + '* var bool = isPositive( 3.0 );', + '* // returns true', + '*', + '* bool = isPositive( -1.0 );', + '* // returns false', + '*/', + 'function isPositive( x ) {', + '\treturn x > 0;', + '}' + ].join( '\n' ), + 'options': OPTS +}; +valid.push( test ); + +// No options provided (rule does nothing): +test = { + 'code': [ + '/**', + '* Squares a number.', + '*', + '* @example', + '* var y = square( 3.0 )', + '*/', + 'function square( x ) {', + '\treturn x * x;', + '}' + ].join( '\n' ) +}; +valid.push( test ); + + +// EXPORTS // + +module.exports = valid; diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/test/test.js b/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/test/test.js new file mode 100644 index 000000000000..db2ad3748540 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/jsdoc-example-eslint/test/test.js @@ -0,0 +1,70 @@ +/** +* @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 tape = require( 'tape' ); +var RuleTester = require( 'eslint' ).RuleTester; +var rule = require( './../lib' ); + + +// FIXTURES // + +var valid = require( './fixtures/valid.js' ); +var invalid = require( './fixtures/invalid.js' ); + + +// TESTS // + +tape( 'main export is an object', function test( t ) { + t.ok( true, __filename ); + t.strictEqual( typeof rule, 'object', 'main export is an object' ); + t.end(); +}); + +tape( 'the rule positively validates JSDoc examples with proper style', function test( t ) { + var tester = new RuleTester(); + + try { + tester.run( 'jsdoc-example-eslint', rule, { + 'valid': valid, + 'invalid': [] + }); + t.pass( 'passed without errors' ); + } catch ( err ) { + t.fail( 'encountered an error: ' + err.message ); + } + t.end(); +}); + +tape( 'the rule negatively validates JSDoc examples with style violations', function test( t ) { + var tester = new RuleTester(); + + try { + tester.run( 'jsdoc-example-eslint', rule, { + 'valid': [], + 'invalid': invalid + }); + t.pass( 'passed without errors' ); + } catch ( err ) { + t.fail( 'encountered an error: ' + err.message ); + } + t.end(); +}); diff --git a/lib/node_modules/@stdlib/_tools/eslint/rules/lib/index.js b/lib/node_modules/@stdlib/_tools/eslint/rules/lib/index.js index fcf6e671c4c9..1375e30b2ffa 100644 --- a/lib/node_modules/@stdlib/_tools/eslint/rules/lib/index.js +++ b/lib/node_modules/@stdlib/_tools/eslint/rules/lib/index.js @@ -216,6 +216,15 @@ setReadOnly( rules, 'jsdoc-emphasis-marker', require( '@stdlib/_tools/eslint/rul */ setReadOnly( rules, 'jsdoc-empty-line-before-example', require( '@stdlib/_tools/eslint/rules/jsdoc-empty-line-before-example' ) ); +/** +* @name jsdoc-example-eslint +* @memberof rules +* @readonly +* @type {Function} +* @see {@link module:@stdlib/_tools/eslint/rules/jsdoc-example-eslint} +*/ +setReadOnly( rules, 'jsdoc-example-eslint', require( '@stdlib/_tools/eslint/rules/jsdoc-example-eslint' ) ); + /** * @name jsdoc-example-require-spacing * @memberof rules diff --git a/tools/make/lib/init/eslint.mk b/tools/make/lib/init/eslint.mk index 059cd131dad9..b9416bd9eb56 100644 --- a/tools/make/lib/init/eslint.mk +++ b/tools/make/lib/init/eslint.mk @@ -57,6 +57,7 @@ $(stdlib_custom_eslint_rules_plugin_out)/index.js: $(NODE_MODULES) $(stdlib_cust $(BROWSERIFY) $(stdlib_custom_eslint_rules_plugin_entry) \ --node \ --ignore-missing \ + --external eslint \ --outfile $@ \ --standalone $(stdlib_custom_eslint_rules_plugin_name)