Skip to content

Commit 586bc1f

Browse files
authored
build: add ESLint rule to enforce that last require statement in JS examples is a relative path
PR-URL: #5379 Closes: stdlib-js/metr-issue-tracker#3 Reviewed-by: Athan Reines <[email protected]>
1 parent bf2f650 commit 586bc1f

File tree

10 files changed

+602
-0
lines changed

10 files changed

+602
-0
lines changed

etc/eslint/.eslintrc.examples.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ eslint.rules[ 'stdlib/doctest' ] = 'error';
9292
*/
9393
eslint.rules[ 'stdlib/vars-order' ] = 'off';
9494

95+
/**
96+
* Enforce that last `require` is a relative path.
97+
*
98+
* @private
99+
*/
100+
eslint.rules[ 'stdlib/require-last-path-relative' ] = 'error';
101+
95102

96103
// EXPORTS //
97104

lib/node_modules/@stdlib/_tools/eslint/rules/lib/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,15 @@ setReadOnly( rules, 'require-file-extensions', require( '@stdlib/_tools/eslint/r
981981
*/
982982
setReadOnly( rules, 'require-globals', require( '@stdlib/_tools/eslint/rules/require-globals' ) );
983983

984+
/**
985+
* @name require-last-path-relative
986+
* @memberof rules
987+
* @readonly
988+
* @type {Function}
989+
* @see {@link module:@stdlib/_tools/eslint/rules/require-last-path-relative}
990+
*/
991+
setReadOnly( rules, 'require-last-path-relative', require( '@stdlib/_tools/eslint/rules/require-last-path-relative' ) );
992+
984993
/**
985994
* @name require-leading-slash
986995
* @memberof rules
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<!--
2+
3+
@license Apache-2.0
4+
5+
Copyright (c) 2025 The Stdlib Authors.
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
19+
-->
20+
21+
# require-last-path-relative
22+
23+
> [ESLint rule][eslint-rules] enforcing that the last `require` statement is a relative path.
24+
25+
<section class="intro">
26+
27+
</section>
28+
29+
<!-- /.intro -->
30+
31+
<section class="usage">
32+
33+
## Usage
34+
35+
```javascript
36+
var rule = require( '@stdlib/_tools/eslint/rules/require-last-path-relative' );
37+
```
38+
39+
#### rule
40+
41+
[ESLint rule][eslint-rules] enforcing that the last `require` statement is a relative path.
42+
43+
**Bad**:
44+
45+
```javascript
46+
var zip = require( '@stdlib/utils/zip' );
47+
var unzip = require( '@stdlib/utils/unzip' );
48+
```
49+
50+
**Good**:
51+
52+
<!-- eslint stdlib/require-file-extensions: "off" -->
53+
54+
```javascript
55+
var zip = require( '@stdlib/utils/zip' );
56+
var unzip = require( './../lib' );
57+
```
58+
59+
The rule is mainly for `examples` files to help make explicit that the API being demonstrated belongs to the respective package.
60+
61+
</section>
62+
63+
<!-- /.usage -->
64+
65+
<section class="examples">
66+
67+
## Examples
68+
69+
<!-- eslint no-undef: "error" -->
70+
71+
```javascript
72+
var Linter = require( 'eslint' ).Linter;
73+
var rule = require( '@stdlib/_tools/eslint/rules/require-last-path-relative' );
74+
75+
var linter = new Linter();
76+
var result;
77+
78+
var code = [
79+
'var beep = require( \'@stdlib/boop/beep\' );',
80+
'var foo = require( \'@stdlib/bar/foo\' );'
81+
].join( '\n' );
82+
83+
linter.defineRule( 'require-last-path-relative', rule );
84+
85+
result = linter.verify( code, {
86+
'rules': {
87+
'require-last-path-relative-relative': 'error'
88+
}
89+
});
90+
/* returns
91+
[
92+
{
93+
'ruleId': 'require-last-path-relative',
94+
'severity': 2,
95+
'message': 'Last `require` statement must be a relative path',
96+
'line': 2,
97+
'column': 11,
98+
'nodeType': 'CallExpression',
99+
'endLine': 2,
100+
'endColumn': 39
101+
}
102+
]
103+
*/
104+
```
105+
106+
</section>
107+
108+
<!-- /.examples -->
109+
110+
<!-- Section for related `stdlib` packages. Do not manually edit this section, as it is automatically populated. -->
111+
112+
<section class="related">
113+
114+
</section>
115+
116+
<!-- /.related -->
117+
118+
<!-- Section for all links. Make sure to keep an empty line after the `section` element and another before the `/section` close. -->
119+
120+
<section class="links">
121+
122+
[eslint-rules]: https://eslint.org/docs/developer-guide/working-with-rules
123+
124+
</section>
125+
126+
<!-- /.links -->
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2025 The Stdlib Authors.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
'use strict';
20+
21+
var Linter = require( 'eslint' ).Linter;
22+
var rule = require( './../lib' );
23+
24+
var linter = new Linter();
25+
26+
var code = [
27+
'var beep = require( \'@stdlib/boop/beep\' );',
28+
'var foo = require( \'@stdlib/bar/foo\' );'
29+
].join( '\n' );
30+
31+
linter.defineRule( 'require-last-path-relative', rule );
32+
33+
var result = linter.verify( code, {
34+
'rules': {
35+
'require-last-path-relative': 'error'
36+
}
37+
});
38+
console.log( result );
39+
/* =>
40+
[
41+
{
42+
'ruleId': 'require-last-path-relative',
43+
'severity': 2,
44+
'message': 'Last `require` statement must be a relative path',
45+
'line': 2,
46+
'column': 11,
47+
'nodeType': 'CallExpression',
48+
'endLine': 2,
49+
'endColumn': 39
50+
}
51+
]
52+
*/
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2025 The Stdlib Authors.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
'use strict';
20+
21+
/**
22+
* ESLint rule to enforce that the last `require` statement is a relative path.
23+
*
24+
* @module @stdlib/_tools/eslint/rules/require-last-path-relative
25+
*
26+
* @example
27+
* var rule = require( '@stdlib/_tools/eslint/rules/require-last-path-relative' );
28+
*
29+
* console.log( rule );
30+
*/
31+
32+
// MODULES //
33+
34+
var main = require( './main.js' );
35+
36+
37+
// EXPORTS //
38+
39+
module.exports = main;
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2025 The Stdlib Authors.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
'use strict';
20+
21+
// MODULES //
22+
23+
var startsWith = require( '@stdlib/string/starts-with' );
24+
25+
26+
// FUNCTIONS //
27+
28+
/**
29+
* Checks if a `require` path is relative.
30+
*
31+
* @private
32+
* @param {string} path - `require` path
33+
* @returns {boolean} boolean indicating if path is relative
34+
*/
35+
function isRelativePath( path ) {
36+
return (
37+
startsWith( path, './' ) ||
38+
startsWith( path, '../' )
39+
);
40+
}
41+
42+
43+
// MAIN //
44+
45+
/**
46+
* ESLint rule to enforce that the last `require` statement is a relative path.
47+
*
48+
* @param {Object} context - ESLint context
49+
* @returns {Object} validators
50+
*/
51+
function main( context ) {
52+
var requires = [];
53+
54+
/**
55+
* Validates `require` statements.
56+
*
57+
* @private
58+
* @param {ASTNode} node - node to examine
59+
*/
60+
function validate( node ) {
61+
var requirePath;
62+
if (
63+
node.callee.name === 'require' &&
64+
node.arguments[ 0 ] &&
65+
node.arguments[ 0 ].type === 'Literal'
66+
) {
67+
requirePath = node.arguments[ 0 ].value;
68+
requires.push({
69+
'node': node,
70+
'path': requirePath,
71+
'isRelative': isRelativePath( requirePath )
72+
});
73+
}
74+
}
75+
76+
/**
77+
* Reports the error message.
78+
*
79+
* @private
80+
* @param {Object} lastRequire - last `require` statement
81+
*/
82+
function report( lastRequire ) {
83+
context.report({
84+
'node': lastRequire.node,
85+
'message': 'Last `require` statement in example files must be a relative path'
86+
});
87+
}
88+
89+
/**
90+
* Callback invoked upon program exit.
91+
*
92+
* @private
93+
*/
94+
function finish() {
95+
var lastRequire;
96+
if ( requires.length > 0 ) {
97+
lastRequire = requires[ requires.length - 1 ];
98+
if ( !lastRequire.isRelative ) {
99+
report( lastRequire );
100+
}
101+
requires.length = 0; // reset to ensure that the array is not used across multiple files
102+
}
103+
}
104+
105+
return {
106+
'CallExpression': validate,
107+
'Program:exit': finish
108+
};
109+
}
110+
111+
112+
// EXPORTS //
113+
114+
module.exports = {
115+
'meta': {
116+
'type': 'suggestion',
117+
'docs': {
118+
'description': 'enforce that the last `require` statement is a relative path'
119+
},
120+
'schema': []
121+
},
122+
'create': main
123+
};

0 commit comments

Comments
 (0)