-
-
Notifications
You must be signed in to change notification settings - Fork 883
build: add tsdoc-doctest
ESLint rule
#8039
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Planeshifter
wants to merge
5
commits into
develop
Choose a base branch
from
philipp/add-typescript-doctest-linting
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,653
−1
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
d4211dd
build: add `tsdoc-doctest` ESLint rule for linting return annotations…
Planeshifter f723627
build: minor refactoring
Planeshifter 9dd2b66
refactor: clean-up and handle interface case
Planeshifter 5f3400b
docs: update comment to avoid non-conventional capitalization
kgryte e79a1ac
docs: add missing period
Planeshifter File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
184 changes: 184 additions & 0 deletions
184
lib/node_modules/@stdlib/_tools/eslint/rules/tsdoc-doctest/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
<!-- | ||
|
||
@license Apache-2.0 | ||
|
||
Copyright (c) 2025 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. | ||
|
||
--> | ||
|
||
# tsdoc-doctest | ||
|
||
> [ESLint rule][eslint-rules] to ensure that return annotations in TSDoc examples match the actual output. | ||
|
||
<section class="intro"> | ||
|
||
</section> | ||
|
||
<!-- /.intro --> | ||
|
||
<section class="usage"> | ||
|
||
## Usage | ||
|
||
```javascript | ||
var rule = require( '@stdlib/_tools/eslint/rules/tsdoc-doctest' ); | ||
``` | ||
|
||
#### rule | ||
|
||
[ESLint rule][eslint-rules] to ensure that return annotations in TSDoc examples match the actual output. The rule validates `@example` blocks in TSDoc comments within `.d.ts` files. | ||
|
||
**Bad**: | ||
|
||
<!-- eslint-disable stdlib/tsdoc-doctest --> | ||
|
||
```typescript | ||
/** | ||
* Adds two numbers. | ||
* | ||
* @param x - first number | ||
* @param y - second number | ||
* @returns sum of x and y | ||
* | ||
* @example | ||
* var result = add( 2, 3 ); | ||
* // returns 6 | ||
*/ | ||
declare function add( x: number, y: number ): number; | ||
|
||
export = add; | ||
``` | ||
|
||
**Good**: | ||
|
||
```typescript | ||
/** | ||
* Adds two numbers. | ||
* | ||
* @param x - first number | ||
* @param y - second number | ||
* @returns sum of x and y | ||
* | ||
* @example | ||
* var result = add( 2, 3 ); | ||
* // returns 5 | ||
*/ | ||
declare function add( x: number, y: number ): number; | ||
|
||
export = add; | ||
``` | ||
|
||
</section> | ||
|
||
<!-- /.usage --> | ||
|
||
<section class="examples"> | ||
|
||
## Examples | ||
|
||
<!-- eslint no-undef: "error" --> | ||
|
||
```javascript | ||
var Linter = require( 'eslint' ).Linter; | ||
var parser = require( '@typescript-eslint/parser' ); | ||
var rule = require( '@stdlib/_tools/eslint/rules/tsdoc-doctest' ); | ||
|
||
var linter = new Linter(); | ||
|
||
// Register the TypeScript parser and ESLint rule: | ||
linter.defineParser( '@typescript-eslint/parser', parser ); | ||
linter.defineRule( 'tsdoc-doctest', rule ); | ||
|
||
// Generate our source code with incorrect return annotation: | ||
var code = [ | ||
'/**', | ||
'* Returns the absolute value of a number.', | ||
'*', | ||
'* @param x - input value', | ||
'* @returns absolute value', | ||
'*', | ||
'* @example', | ||
'* var result = abs( -3 );', | ||
'* // returns 2', | ||
'*/', | ||
'declare function abs( x: number ): number;', | ||
'', | ||
'export = abs;' | ||
].join( '\n' ); | ||
|
||
// Lint the code: | ||
var result = linter.verify( code, { | ||
'parser': '@typescript-eslint/parser', | ||
'parserOptions': { | ||
'ecmaVersion': 2018, | ||
'sourceType': 'module' | ||
}, | ||
'rules': { | ||
'tsdoc-doctest': 'error' | ||
} | ||
}, { | ||
'filename': 'lib/node_modules/@stdlib/math/base/special/abs/docs/types/index.d.ts' | ||
}); | ||
/* returns | ||
[ | ||
{ | ||
'ruleId': 'tsdoc-doctest', | ||
'severity': 2, | ||
'message': 'Displayed return value is `2`, but expected `3` instead', | ||
'line': 9, | ||
'column': 1, | ||
'nodeType': null, | ||
'endLine': 10, | ||
'endColumn': 37 | ||
} | ||
] | ||
*/ | ||
``` | ||
|
||
</section> | ||
|
||
<!-- /.examples --> | ||
|
||
<section class="notes"> | ||
|
||
## Notes | ||
|
||
- The corresponding JavaScript package must be loadable via `require('@stdlib/<package>')`. | ||
- The rule skips validation if the package cannot be loaded. | ||
- Examples are executed in a sandboxed VM context with limited globals. | ||
- The rule validates assignment-form examples (e.g., `var x = fn(...);` followed by an annotation). It does not validate console output or expression-only forms using `// =>`. | ||
- The implementation path the rule uses to load the JavaScript implementation can be overridden via the `implementationPath` rule option (default: `../../lib` relative to the declaration file). | ||
Planeshifter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
</section> | ||
|
||
<!-- /.notes --> | ||
|
||
<!-- Section for related `stdlib` packages. Do not manually edit this section, as it is automatically populated. --> | ||
|
||
<section class="related"> | ||
|
||
</section> | ||
|
||
<!-- /.related --> | ||
|
||
<!-- Section for all links. Make sure to keep an empty line after the `section` element and another before the `/section` close. --> | ||
|
||
<section class="links"> | ||
|
||
[eslint-rules]: https://eslint.org/docs/developer-guide/working-with-rules | ||
|
||
</section> | ||
|
||
<!-- /.links --> |
76 changes: 76 additions & 0 deletions
76
lib/node_modules/@stdlib/_tools/eslint/rules/tsdoc-doctest/examples/index.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/** | ||
* @license Apache-2.0 | ||
* | ||
* Copyright (c) 2025 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 parser = require( '@typescript-eslint/parser' ); | ||
var rule = require( './../lib' ); | ||
|
||
var linter = new Linter(); | ||
|
||
// Register the TypeScript parser and ESLint rule: | ||
linter.defineParser( '@typescript-eslint/parser', parser ); | ||
linter.defineRule( 'tsdoc-doctest', rule ); | ||
|
||
// Generate our source code with incorrect return annotation: | ||
var code = [ | ||
'/**', | ||
'* Returns the absolute value of a number.', | ||
'*', | ||
'* @param x - input value', | ||
'* @returns absolute value', | ||
'*', | ||
'* @example', | ||
'* var result = abs( -3 );', | ||
'* // returns 2', | ||
'*/', | ||
'declare function abs( x: number ): number;', | ||
'', | ||
'export = abs;' | ||
].join( '\n' ); | ||
|
||
// Lint the code: | ||
var result = linter.verify( code, { | ||
'parser': '@typescript-eslint/parser', | ||
'parserOptions': { | ||
'ecmaVersion': 2018, | ||
'sourceType': 'module' | ||
}, | ||
'rules': { | ||
'tsdoc-doctest': 'error' | ||
} | ||
}, { | ||
'filename': 'lib/node_modules/@stdlib/math/base/special/abs/docs/types/index.d.ts' | ||
}); | ||
|
||
console.log( result ); | ||
/* => | ||
[ | ||
{ | ||
'ruleId': 'tsdoc-doctest', | ||
'severity': 2, | ||
'message': 'Displayed return value is `2`, but expected `3` instead', | ||
'line': 9, | ||
'column': 1, | ||
'nodeType': null, | ||
'endLine': 10, | ||
'endColumn': 37 | ||
} | ||
] | ||
*/ |
90 changes: 90 additions & 0 deletions
90
lib/node_modules/@stdlib/_tools/eslint/rules/tsdoc-doctest/lib/add_package_to_scope.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/** | ||
* @license Apache-2.0 | ||
* | ||
* Copyright (c) 2025 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 // | ||
|
||
// Regular expressions for matching TypeScript declarations: | ||
var RE_DECLARE_FUNCTION = /declare\s+function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*[<(]/; | ||
var RE_DECLARE_VAR = /declare\s+var\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/; | ||
var RE_DECLARE_CLASS = /declare\s+class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s/; | ||
var RE_DECLARE_VAR_NAMESPACE = /declare\s+var\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:\s*[A-Z][a-zA-Z0-9_$]*/; | ||
Planeshifter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
var RE_DECLARE_CONST = /declare\s+const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/; | ||
var RE_DECLARE_VAR_INTERFACE = /declare\s+var\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:\s*([A-Z][a-zA-Z0-9_$]*)/; | ||
|
||
|
||
// MAIN // | ||
|
||
/** | ||
* Adds package export to scope based on TypeScript declarations. | ||
* | ||
* @param {Object} scope - VM scope object to add the package export to | ||
* @param {*} pkg - package export value to be added to scope | ||
* @param {string} sourceText - TypeScript declaration source text to parse for identifier names | ||
*/ | ||
function addPackageToScope( scope, pkg, sourceText ) { | ||
var interfaceMatch; | ||
var namespaceMatch; | ||
var funcMatch; | ||
|
||
if ( typeof pkg === 'function' ) { | ||
// Try to match declare function pattern (handles generics with <): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to L60, it would be nice to include an example in each of the comments (L50, L54, etc) which demonstrate what pattern we are attempting to match. |
||
funcMatch = sourceText.match( RE_DECLARE_FUNCTION ); | ||
if ( !funcMatch ) { | ||
// Try to match declare var pattern: | ||
funcMatch = sourceText.match( RE_DECLARE_VAR ); | ||
} | ||
if ( !funcMatch ) { | ||
// Try to match declare class pattern (for constructor functions): | ||
funcMatch = sourceText.match( RE_DECLARE_CLASS ); | ||
} | ||
if ( funcMatch ) { | ||
scope[ funcMatch[1] ] = pkg; | ||
} | ||
// Also check for declare var with interface pattern (e.g., declare var ctor: Int32Vector): | ||
interfaceMatch = sourceText.match( RE_DECLARE_VAR_INTERFACE ); | ||
if ( interfaceMatch ) { | ||
// Make the function available under both the variable name and the interface name: | ||
scope[ interfaceMatch[1] ] = pkg; // e.g., ctor | ||
scope[ interfaceMatch[2] ] = pkg; // e.g., Int32Vector | ||
} | ||
} else if ( typeof pkg === 'object' && pkg !== null ) { | ||
// Handle namespace objects and other object interfaces: | ||
namespaceMatch = sourceText.match( RE_DECLARE_VAR_NAMESPACE ); | ||
if ( namespaceMatch ) { | ||
scope[ namespaceMatch[1] ] = pkg; | ||
} | ||
// Also check for const declarations (e.g., Complex64/Complex128 constants): | ||
funcMatch = sourceText.match( RE_DECLARE_CONST ); | ||
if ( funcMatch ) { | ||
scope[ funcMatch[1] ] = pkg; | ||
} | ||
} else { | ||
// Try to match declare const pattern: | ||
funcMatch = sourceText.match( RE_DECLARE_CONST ); | ||
if ( funcMatch ) { | ||
scope[ funcMatch[1] ] = pkg; | ||
} | ||
} | ||
} | ||
|
||
|
||
// EXPORTS // | ||
|
||
module.exports = addPackageToScope; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this limitation?