@eslint-sh/eslint-plugin 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +223 -17
- package/dist/command-harvest/builtins/bash.d.ts +36 -0
- package/dist/command-harvest/builtins/bash.js +98 -0
- package/dist/command-harvest/builtins/bats.d.ts +45 -0
- package/dist/command-harvest/builtins/bats.js +33 -0
- package/dist/command-harvest/builtins/index.d.ts +18 -0
- package/dist/command-harvest/builtins/index.js +39 -0
- package/dist/command-harvest/builtins/posix.d.ts +51 -0
- package/dist/command-harvest/builtins/posix.js +93 -0
- package/dist/command-harvest/harvest-commands.d.ts +66 -0
- package/dist/command-harvest/harvest-commands.js +97 -0
- package/dist/command-harvest/wrapper-table.d.ts +24 -0
- package/dist/command-harvest/wrapper-table.js +49 -0
- package/dist/configs/agent-authored.d.ts +41 -0
- package/dist/configs/agent-authored.js +33 -0
- package/dist/configs/bash-3-2.d.ts +28 -0
- package/dist/configs/bash-3-2.js +50 -0
- package/dist/configs/posix-portable.d.ts +40 -0
- package/dist/configs/posix-portable.js +52 -0
- package/dist/configs/recommended.d.ts +52 -0
- package/dist/configs/recommended.js +34 -0
- package/dist/declaration-conventions/comment-pragma.d.ts +33 -0
- package/dist/declaration-conventions/comment-pragma.js +153 -0
- package/dist/declaration-conventions/declaration-convention.d.ts +189 -0
- package/dist/declaration-conventions/declaration-convention.js +1 -0
- package/dist/declaration-conventions/read-declared-commands.d.ts +70 -0
- package/dist/declaration-conventions/read-declared-commands.js +128 -0
- package/dist/eslint-plugin-syntax-analyze.d.ts +23 -0
- package/dist/eslint-plugin-syntax.d.ts +27 -0
- package/dist/eslint-plugin.d.ts +1211 -52
- package/dist/index.d.ts +56 -8
- package/dist/index.js +53 -6
- package/dist/language/command-resolution.d.ts +124 -0
- package/dist/language/command-resolution.js +120 -0
- package/dist/language/end-of-input-marker.d.ts +29 -0
- package/dist/language/end-of-input-marker.js +88 -0
- package/dist/language/line-breaks.d.ts +18 -0
- package/dist/language/line-breaks.js +22 -0
- package/dist/language/lint-view-types.d.ts +104 -0
- package/dist/language/lint-view-types.js +1 -0
- package/dist/language/non-shell-file-guard.d.ts +35 -0
- package/dist/language/non-shell-file-guard.js +59 -0
- package/dist/language/selector-classes.d.ts +0 -1
- package/dist/language/selector-classes.js +0 -1
- package/dist/language/shell-language-options.d.ts +7 -1
- package/dist/language/shell-language-options.js +4 -4
- package/dist/language/shell-language.d.ts +10 -2
- package/dist/language/shell-language.js +59 -8
- package/dist/language/shell-source-code.d.ts +342 -5
- package/dist/language/shell-source-code.js +559 -30
- package/dist/language/source-coordinates.d.ts +13 -2
- package/dist/language/source-coordinates.js +68 -14
- package/dist/language/syntax-view.d.ts +60 -39
- package/dist/language/syntax-view.js +200 -45
- package/dist/language/word-resolution.d.ts +4 -3
- package/dist/language/word-resolution.js +4 -3
- package/dist/rules/compat/create-compat-rule.d.ts +59 -0
- package/dist/rules/compat/create-compat-rule.js +65 -0
- package/dist/rules/compat/declaration-options.d.ts +19 -0
- package/dist/rules/compat/declaration-options.js +110 -0
- package/dist/rules/compat/no-append-all-redirect.d.ts +13 -0
- package/dist/rules/compat/no-append-all-redirect.js +73 -0
- package/dist/rules/compat/no-associative-arrays.d.ts +12 -0
- package/dist/rules/compat/no-associative-arrays.js +39 -0
- package/dist/rules/compat/no-case-attributes.d.ts +12 -0
- package/dist/rules/compat/no-case-attributes.js +63 -0
- package/dist/rules/compat/no-case-fallthrough.d.ts +12 -0
- package/dist/rules/compat/no-case-fallthrough.js +67 -0
- package/dist/rules/compat/no-case-modification.d.ts +35 -0
- package/dist/rules/compat/no-case-modification.js +86 -0
- package/dist/rules/compat/no-coproc.d.ts +12 -0
- package/dist/rules/compat/no-coproc.js +47 -0
- package/dist/rules/compat/no-declare-g.d.ts +12 -0
- package/dist/rules/compat/no-declare-g.js +59 -0
- package/dist/rules/compat/no-double-bracket.d.ts +12 -0
- package/dist/rules/compat/no-double-bracket.js +52 -0
- package/dist/rules/compat/no-epoch-variables.d.ts +12 -0
- package/dist/rules/compat/no-epoch-variables.js +57 -0
- package/dist/rules/compat/no-fd-variable.d.ts +12 -0
- package/dist/rules/compat/no-fd-variable.js +48 -0
- package/dist/rules/compat/no-globstar.d.ts +12 -0
- package/dist/rules/compat/no-globstar.js +39 -0
- package/dist/rules/compat/no-lastpipe-semantics.d.ts +12 -0
- package/dist/rules/compat/no-lastpipe-semantics.js +53 -0
- package/dist/rules/compat/no-let.d.ts +12 -0
- package/dist/rules/compat/no-let.js +62 -0
- package/dist/rules/compat/no-mapfile.d.ts +13 -0
- package/dist/rules/compat/no-mapfile.js +122 -0
- package/dist/rules/compat/no-nameref.d.ts +12 -0
- package/dist/rules/compat/no-nameref.js +44 -0
- package/dist/rules/compat/no-negative-array-index.d.ts +13 -0
- package/dist/rules/compat/no-negative-array-index.js +123 -0
- package/dist/rules/compat/no-nounset-empty-array.d.ts +13 -0
- package/dist/rules/compat/no-nounset-empty-array.js +158 -0
- package/dist/rules/compat/no-parameter-transformation.d.ts +13 -0
- package/dist/rules/compat/no-parameter-transformation.js +123 -0
- package/dist/rules/compat/no-pipe-all.d.ts +13 -0
- package/dist/rules/compat/no-pipe-all.js +68 -0
- package/dist/rules/compat/no-printf-time-format.d.ts +12 -0
- package/dist/rules/compat/no-printf-time-format.js +99 -0
- package/dist/rules/compat/no-source.d.ts +13 -0
- package/dist/rules/compat/no-source.js +103 -0
- package/dist/rules/compat/no-test-v.d.ts +12 -0
- package/dist/rules/compat/no-test-v.js +280 -0
- package/dist/rules/compat/no-unicode-escape.d.ts +14 -0
- package/dist/rules/compat/no-unicode-escape.js +120 -0
- package/dist/rules/compat/no-wait-n.d.ts +12 -0
- package/dist/rules/compat/no-wait-n.js +73 -0
- package/dist/rules/compat/operator-range.d.ts +10 -0
- package/dist/rules/compat/operator-range.js +15 -0
- package/dist/rules/compat/posix-absent-command.d.ts +12 -0
- package/dist/rules/compat/posix-absent-command.js +134 -0
- package/dist/rules/compat/shopt-options.d.ts +9 -0
- package/dist/rules/compat/shopt-options.js +63 -0
- package/dist/rules/compat/version-gates.d.ts +76 -0
- package/dist/rules/compat/version-gates.js +197 -0
- package/dist/rules/create-rule.d.ts +91 -0
- package/dist/rules/create-rule.js +51 -0
- package/dist/rules/create-wrapper-rule.d.ts +166 -0
- package/dist/rules/create-wrapper-rule.js +407 -0
- package/dist/rules/define-shell-rule.d.ts +184 -0
- package/dist/rules/define-shell-rule.js +56 -0
- package/dist/rules/index.d.ts +26 -0
- package/dist/rules/index.js +88 -0
- package/dist/rules/max-script-complexity.d.ts +28 -0
- package/dist/rules/max-script-complexity.js +130 -0
- package/dist/rules/no-dynamic-invocation.d.ts +35 -0
- package/dist/rules/no-dynamic-invocation.js +117 -0
- package/dist/rules/no-eval.d.ts +13 -0
- package/dist/rules/no-eval.js +89 -0
- package/dist/rules/no-masked-exit-status.d.ts +21 -0
- package/dist/rules/no-masked-exit-status.js +667 -0
- package/dist/rules/no-secret-env-reads.d.ts +24 -0
- package/dist/rules/no-secret-env-reads.js +133 -0
- package/dist/rules/no-secret-flags.d.ts +32 -0
- package/dist/rules/no-secret-flags.js +162 -0
- package/dist/rules/no-unchecked-cd.d.ts +44 -0
- package/dist/rules/no-unchecked-cd.js +250 -0
- package/dist/rules/no-undeclared-commands.d.ts +20 -0
- package/dist/rules/no-undeclared-commands.js +115 -0
- package/dist/rules/no-unused-declarations.d.ts +23 -0
- package/dist/rules/no-unused-declarations.js +259 -0
- package/dist/rules/prefer-local.d.ts +16 -0
- package/dist/rules/prefer-local.js +249 -0
- package/dist/rules/quote-expansions.d.ts +13 -0
- package/dist/rules/quote-expansions.js +229 -0
- package/dist/rules/require-network-wrapper.d.ts +40 -0
- package/dist/rules/require-network-wrapper.js +244 -0
- package/dist/rules/require-shebang.d.ts +29 -0
- package/dist/rules/require-shebang.js +101 -0
- package/dist/rules/require-strict-mode.d.ts +52 -0
- package/dist/rules/require-strict-mode.js +177 -0
- package/dist/rules/require-tty-guard.d.ts +13 -0
- package/dist/rules/require-tty-guard.js +289 -0
- package/dist/rules/resolve-declaration-convention.d.ts +96 -0
- package/dist/rules/resolve-declaration-convention.js +109 -0
- package/dist/rules/resolve-network-wrapper.d.ts +19 -0
- package/dist/rules/resolve-network-wrapper.js +116 -0
- package/dist/rules/set-options.d.ts +109 -0
- package/dist/rules/set-options.js +201 -0
- package/dist/rules/shebang-interpreter.d.ts +18 -0
- package/dist/rules/shebang-interpreter.js +81 -0
- package/dist/rules/shebang-matches-dialect.d.ts +12 -0
- package/dist/rules/shebang-matches-dialect.js +69 -0
- package/dist/rules/transparent-wrappers.d.ts +52 -0
- package/dist/rules/transparent-wrappers.js +99 -0
- package/dist/rules/user-pattern.d.ts +63 -0
- package/dist/rules/user-pattern.js +184 -0
- package/dist/syntax/analyze.d.ts +19 -0
- package/dist/syntax/analyze.js +21 -0
- package/dist/syntax/index.d.ts +23 -0
- package/dist/syntax/index.js +25 -0
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -1
- package/docs/compat.md +347 -0
- package/docs/custom-rule-tutorial.md +780 -34
- package/docs/declaration-conventions.md +478 -0
- package/docs/examples/audit-wrapper-pack.mjs +15 -0
- package/docs/examples/dep-line-convention.mjs +22 -0
- package/docs/examples/eslint.config.mjs +3 -2
- package/docs/examples/no-and-or-as-if-rule.mjs +61 -0
- package/docs/examples/no-backticks-rule-tester.mjs +106 -0
- package/docs/examples/no-backticks-rule.mjs +60 -0
- package/docs/examples/no-curl-in-functions-rule.mjs +53 -0
- package/docs/examples/no-function-keyword-rule.mjs +59 -0
- package/docs/examples/no-listed-commands-rule.ts +63 -0
- package/docs/examples/no-rm-rf-rule.mjs +26 -15
- package/docs/examples/no-unquoted-rm-variable-rule.ts +54 -0
- package/docs/examples/sidecar-convention.mjs +38 -0
- package/docs/examples/sidecar-snapshot-convention.mjs +26 -0
- package/docs/examples/sorted-declarations-rule.mjs +52 -0
- package/docs/reading-nodes.md +159 -0
- package/docs/rules/README.md +78 -0
- package/docs/rules/_template.md +83 -0
- package/docs/rules/compat/no-append-all-redirect.md +87 -0
- package/docs/rules/compat/no-associative-arrays.md +95 -0
- package/docs/rules/compat/no-case-attributes.md +82 -0
- package/docs/rules/compat/no-case-fallthrough.md +90 -0
- package/docs/rules/compat/no-case-modification.md +74 -0
- package/docs/rules/compat/no-coproc.md +76 -0
- package/docs/rules/compat/no-declare-g.md +80 -0
- package/docs/rules/compat/no-double-bracket.md +72 -0
- package/docs/rules/compat/no-epoch-variables.md +79 -0
- package/docs/rules/compat/no-fd-variable.md +78 -0
- package/docs/rules/compat/no-globstar.md +74 -0
- package/docs/rules/compat/no-lastpipe-semantics.md +73 -0
- package/docs/rules/compat/no-let.md +78 -0
- package/docs/rules/compat/no-mapfile.md +118 -0
- package/docs/rules/compat/no-nameref.md +81 -0
- package/docs/rules/compat/no-negative-array-index.md +87 -0
- package/docs/rules/compat/no-nounset-empty-array.md +103 -0
- package/docs/rules/compat/no-parameter-transformation.md +94 -0
- package/docs/rules/compat/no-pipe-all.md +85 -0
- package/docs/rules/compat/no-printf-time-format.md +75 -0
- package/docs/rules/compat/no-source.md +98 -0
- package/docs/rules/compat/no-test-v.md +82 -0
- package/docs/rules/compat/no-unicode-escape.md +91 -0
- package/docs/rules/compat/no-wait-n.md +85 -0
- package/docs/rules/max-script-complexity.md +109 -0
- package/docs/rules/no-dynamic-invocation.md +139 -0
- package/docs/rules/no-eval.md +106 -0
- package/docs/rules/no-masked-exit-status.md +239 -0
- package/docs/rules/no-secret-env-reads.md +173 -0
- package/docs/rules/no-secret-flags.md +169 -0
- package/docs/rules/no-unchecked-cd.md +146 -0
- package/docs/rules/no-undeclared-commands.md +141 -0
- package/docs/rules/no-unused-declarations.md +216 -0
- package/docs/rules/prefer-local.md +221 -0
- package/docs/rules/quote-expansions.md +157 -0
- package/docs/rules/require-network-wrapper.md +171 -0
- package/docs/rules/require-shebang.md +157 -0
- package/docs/rules/require-strict-mode.md +253 -0
- package/docs/rules/require-tty-guard.md +128 -0
- package/docs/rules/shebang-matches-dialect.md +120 -0
- package/docs/selectors.md +2 -2
- package/docs/shellcheck.md +86 -0
- package/docs/wrapper-rules.md +206 -0
- package/package.json +26 -20
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/language/selector-classes.d.ts.map +0 -1
- package/dist/language/selector-classes.js.map +0 -1
- package/dist/language/shell-language-options.d.ts.map +0 -1
- package/dist/language/shell-language-options.js.map +0 -1
- package/dist/language/shell-language.d.ts.map +0 -1
- package/dist/language/shell-language.js.map +0 -1
- package/dist/language/shell-source-code.d.ts.map +0 -1
- package/dist/language/shell-source-code.js.map +0 -1
- package/dist/language/source-coordinates.d.ts.map +0 -1
- package/dist/language/source-coordinates.js.map +0 -1
- package/dist/language/syntax-view.d.ts.map +0 -1
- package/dist/language/syntax-view.js.map +0 -1
- package/dist/language/word-resolution.d.ts.map +0 -1
- package/dist/language/word-resolution.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "test your rule" example from the custom-rule tutorial
|
|
3
|
+
* (`../custom-rule-tutorial.md`, step 11). It tests the step 8 rule
|
|
4
|
+
* (`./no-backticks-rule.mjs`) with ESLint's own `RuleTester`, and a small
|
|
5
|
+
* inline rule that offers a suggestion rather than an automatic fix.
|
|
6
|
+
*
|
|
7
|
+
* `RuleTester` needs two settings to run shell rules: the plugin registered
|
|
8
|
+
* under `plugins`, and `language: 'sh/sh'`. Leaving either out
|
|
9
|
+
* makes `RuleTester` throw a configuration error before it checks any case;
|
|
10
|
+
* the tutorial quotes both messages.
|
|
11
|
+
*
|
|
12
|
+
* Run it with `node no-backticks-rule-tester.mjs`. `RuleTester` uses
|
|
13
|
+
* `describe`/`it` when the test runner defines them, and otherwise throws on
|
|
14
|
+
* the first failing case, so a quiet exit means every case passed.
|
|
15
|
+
*
|
|
16
|
+
* This file and `./no-backticks-rule.mjs` ship in the `@eslint-sh/eslint-plugin`
|
|
17
|
+
* package only (`docs/examples/`); the umbrella `eslint-sh` package does not
|
|
18
|
+
* include them. To import from the umbrella instead, change the plugin import
|
|
19
|
+
* below and the one in `./no-backticks-rule.mjs` to `eslint-sh`.
|
|
20
|
+
*
|
|
21
|
+
* @see ../custom-rule-tutorial.md — the tutorial this example backs
|
|
22
|
+
* @see https://eslint.org/docs/latest/integrate/nodejs-api#ruletester
|
|
23
|
+
*/
|
|
24
|
+
import { RuleTester } from 'eslint';
|
|
25
|
+
import sh, { defineShellRule } from '@eslint-sh/eslint-plugin';
|
|
26
|
+
import noBackticks from './no-backticks-rule.mjs';
|
|
27
|
+
|
|
28
|
+
const tester = new RuleTester({
|
|
29
|
+
plugins: { sh },
|
|
30
|
+
language: 'sh/sh',
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
tester.run('no-backticks', noBackticks, {
|
|
34
|
+
valid: [
|
|
35
|
+
// An empty file, and a command substitution that already uses `$(…)`.
|
|
36
|
+
{ code: '' },
|
|
37
|
+
{ code: 'now=$(date +%s)\n' },
|
|
38
|
+
],
|
|
39
|
+
invalid: [
|
|
40
|
+
{
|
|
41
|
+
code: 'now=`date +%s`\n',
|
|
42
|
+
// `messageId` checks which message fired; `line` and `column` check where.
|
|
43
|
+
errors: [{ messageId: 'backticks', line: 1, column: 5 }],
|
|
44
|
+
// `output` is the file after ESLint applies the fix.
|
|
45
|
+
output: 'now=$(date +%s)\n',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
// Multibyte text before and inside the substitutions.
|
|
49
|
+
code: 'echo "héllo `id -u` 🎉" `date`\n',
|
|
50
|
+
errors: [{ messageId: 'backticks' }, { messageId: 'backticks' }],
|
|
51
|
+
output: 'echo "héllo $(id -u) 🎉" $(date)\n',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A rule that only suggests: it reports `which` and offers `command -v`, which
|
|
58
|
+
* the reader chooses to apply. A rule with suggestions declares
|
|
59
|
+
* `hasSuggestions: true`.
|
|
60
|
+
*/
|
|
61
|
+
const noWhich = defineShellRule({
|
|
62
|
+
meta: {
|
|
63
|
+
type: 'suggestion',
|
|
64
|
+
hasSuggestions: true,
|
|
65
|
+
docs: { description: 'prefer `command -v` over `which`' },
|
|
66
|
+
schema: [],
|
|
67
|
+
messages: {
|
|
68
|
+
noWhich: '`which` is not portable.',
|
|
69
|
+
useCommandV: 'Use `command -v` instead.',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
create(context) {
|
|
73
|
+
return {
|
|
74
|
+
CallExpr(node) {
|
|
75
|
+
const [name] = node.args ?? [];
|
|
76
|
+
if (name === undefined || context.sourceCode.getText(name) !== 'which') {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
context.report({
|
|
80
|
+
node: name,
|
|
81
|
+
messageId: 'noWhich',
|
|
82
|
+
suggest: [
|
|
83
|
+
{ messageId: 'useCommandV', fix: (fixer) => fixer.replaceText(name, 'command -v') },
|
|
84
|
+
],
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
tester.run('no-which', noWhich, {
|
|
92
|
+
valid: [{ code: 'command -v ls\n' }],
|
|
93
|
+
invalid: [
|
|
94
|
+
{
|
|
95
|
+
code: 'which ls\n',
|
|
96
|
+
// `output: null` asserts that the rule has no automatic fix.
|
|
97
|
+
output: null,
|
|
98
|
+
errors: [
|
|
99
|
+
{
|
|
100
|
+
messageId: 'noWhich',
|
|
101
|
+
suggestions: [{ messageId: 'useCommandV', output: 'command -v ls\n' }],
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "printed fix" custom rule from the custom-rule tutorial
|
|
3
|
+
* (`../custom-rule-tutorial.md`, step 8). Plain, directly-runnable
|
|
4
|
+
* JavaScript; this package's test suite imports this exact file and
|
|
5
|
+
* asserts the tutorial's code fence matches it byte-for-byte.
|
|
6
|
+
*
|
|
7
|
+
* It reports backtick command substitution (`` `date` ``) and fixes it to
|
|
8
|
+
* `$(date)`. Instead of rewriting text by hand, which would have to re-escape
|
|
9
|
+
* nested backticks and backslashes, the fix edits a copy of the substitution's
|
|
10
|
+
* sh-ast record from `getEditableSyntax` and lets sh-ast's printer write the
|
|
11
|
+
* new text with `getPrintedReplacement`. The original syntax every other rule
|
|
12
|
+
* reads stays unchanged.
|
|
13
|
+
*
|
|
14
|
+
* @import { PrintOptions } from '@eslint-sh/eslint-plugin/syntax'
|
|
15
|
+
* @see ../custom-rule-tutorial.md — the tutorial this rule backs
|
|
16
|
+
*/
|
|
17
|
+
import { defineShellRule } from '@eslint-sh/eslint-plugin';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Prints nested lines with two spaces per level instead of the printer's
|
|
21
|
+
* default tabs.
|
|
22
|
+
* @type {PrintOptions}
|
|
23
|
+
*/
|
|
24
|
+
const printOptions = { Indent: 2 };
|
|
25
|
+
|
|
26
|
+
const noBackticks = defineShellRule({
|
|
27
|
+
meta: {
|
|
28
|
+
type: 'suggestion',
|
|
29
|
+
fixable: 'code',
|
|
30
|
+
docs: {
|
|
31
|
+
description: 'prefer `$(…)` over backtick command substitution',
|
|
32
|
+
},
|
|
33
|
+
schema: [],
|
|
34
|
+
messages: {
|
|
35
|
+
backticks: 'Use `$(…)` instead of backticks for command substitution.',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
create(context) {
|
|
39
|
+
const { sourceCode } = context;
|
|
40
|
+
return {
|
|
41
|
+
CmdSubst(node) {
|
|
42
|
+
if (!node.backquotes) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// An independent copy: editing it changes neither the lint view nor
|
|
46
|
+
// the syntax other rules read.
|
|
47
|
+
const copy = sourceCode.getEditableSyntax(node);
|
|
48
|
+
copy.Backquotes = false;
|
|
49
|
+
const { range, text } = sourceCode.getPrintedReplacement(node, copy, printOptions);
|
|
50
|
+
context.report({
|
|
51
|
+
node,
|
|
52
|
+
messageId: 'backticks',
|
|
53
|
+
fix: (fixer) => fixer.replaceTextRange(range, text),
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export default noBackticks;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "every command in the file" custom rule from the custom-rule tutorial
|
|
3
|
+
* (`../custom-rule-tutorial.md`, step 5). Plain, directly-runnable
|
|
4
|
+
* JavaScript; this package's test suite imports this exact file and
|
|
5
|
+
* asserts the tutorial's code fence matches it byte-for-byte.
|
|
6
|
+
*
|
|
7
|
+
* It reports `curl` run anywhere inside a shell function, including through a
|
|
8
|
+
* wrapper (`sudo curl …`) or inside a command substitution in that function.
|
|
9
|
+
* Instead of tracking "am I inside a function?" while visiting nodes, it reads
|
|
10
|
+
* every command once from `context.sourceCode.getCommandSites()`, whose
|
|
11
|
+
* `context` frames (outermost first) already say how each command is reached.
|
|
12
|
+
*
|
|
13
|
+
* @see ../custom-rule-tutorial.md — the tutorial this rule backs
|
|
14
|
+
*/
|
|
15
|
+
import { defineShellRule } from '@eslint-sh/eslint-plugin';
|
|
16
|
+
|
|
17
|
+
const noCurlInFunctions = defineShellRule({
|
|
18
|
+
meta: {
|
|
19
|
+
type: 'suggestion',
|
|
20
|
+
docs: {
|
|
21
|
+
description: 'disallow `curl` inside shell functions',
|
|
22
|
+
},
|
|
23
|
+
schema: [],
|
|
24
|
+
messages: {
|
|
25
|
+
curlInFunction: 'Function `{{name}}` runs `curl`; fetch data before calling it.',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
create(context) {
|
|
29
|
+
return {
|
|
30
|
+
File() {
|
|
31
|
+
for (const site of context.sourceCode.getCommandSites()) {
|
|
32
|
+
const { effective } = site.resolveInvokedCommand();
|
|
33
|
+
if (!effective.static || effective.text !== 'curl') {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Frames run outermost first, so the last `function` frame is the
|
|
38
|
+
// function that directly contains this command.
|
|
39
|
+
const fn = site.context.findLast((frame) => frame.kind === 'function');
|
|
40
|
+
if (fn) {
|
|
41
|
+
context.report({
|
|
42
|
+
node: site.node,
|
|
43
|
+
messageId: 'curlInFunction',
|
|
44
|
+
data: { name: fn.name },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export default noCurlInFunctions;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "position-based text edit" custom rule from the custom-rule tutorial
|
|
3
|
+
* (`../custom-rule-tutorial.md`, step 9). Plain, directly-runnable
|
|
4
|
+
* JavaScript; this package's test suite imports this exact file and
|
|
5
|
+
* asserts the tutorial's code fence matches it byte-for-byte.
|
|
6
|
+
*
|
|
7
|
+
* It reports the bash-only `function name {` form and fixes it to the
|
|
8
|
+
* portable `name() {`. The fix replaces only the text from the `function`
|
|
9
|
+
* keyword through the name, so the body keeps its exact layout, comments and
|
|
10
|
+
* all; printing the whole edited function would re-indent the body instead.
|
|
11
|
+
* The two ends of that text come from the sh-ast record's own positions,
|
|
12
|
+
* converted to string offsets with `getIndexFromSyntaxPos` (sh-ast positions
|
|
13
|
+
* count UTF-8 bytes).
|
|
14
|
+
*
|
|
15
|
+
* @see ../custom-rule-tutorial.md — the tutorial this rule backs
|
|
16
|
+
*/
|
|
17
|
+
import { defineShellRule } from '@eslint-sh/eslint-plugin';
|
|
18
|
+
|
|
19
|
+
const noFunctionKeyword = defineShellRule({
|
|
20
|
+
meta: {
|
|
21
|
+
type: 'suggestion',
|
|
22
|
+
fixable: 'code',
|
|
23
|
+
docs: {
|
|
24
|
+
description: 'prefer `name() { … }` over the `function` keyword',
|
|
25
|
+
},
|
|
26
|
+
schema: [],
|
|
27
|
+
messages: {
|
|
28
|
+
useParens: 'Declare `{{name}}` as `{{name}}() { … }`, without the `function` keyword.',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
create(context) {
|
|
32
|
+
const { sourceCode } = context;
|
|
33
|
+
return {
|
|
34
|
+
FuncDecl(node) {
|
|
35
|
+
const decl = sourceCode.getSyntax(node);
|
|
36
|
+
// Only `function name {`: not `function name() {`, `name() {`, or
|
|
37
|
+
// zsh's several-name form.
|
|
38
|
+
if (!decl.RsrvWord || decl.Parens || decl.Name === null || decl.Names !== null) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// A name that is not valid UTF-8 arrives as bytes, not a string.
|
|
42
|
+
const name = decl.Name.Value;
|
|
43
|
+
if (typeof name !== 'string') {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const start = sourceCode.getIndexFromSyntaxPos(decl.Position);
|
|
47
|
+
const end = sourceCode.getIndexFromSyntaxPos(decl.Name.ValueEnd);
|
|
48
|
+
context.report({
|
|
49
|
+
node,
|
|
50
|
+
messageId: 'useParens',
|
|
51
|
+
data: { name },
|
|
52
|
+
fix: (fixer) => fixer.replaceTextRange([start, end], `${name}()`),
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export default noFunctionKeyword;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TypeScript custom rule from the custom-rule tutorial
|
|
3
|
+
* (`../custom-rule-tutorial.md`, step 10). This package's test suite
|
|
4
|
+
* asserts the tutorial's code fence matches this file byte-for-byte,
|
|
5
|
+
* type-checks it against the package's published declarations, and runs it
|
|
6
|
+
* through a real `eslint` `Linter`.
|
|
7
|
+
*
|
|
8
|
+
* It reports any command named in its `commands` option, including through a
|
|
9
|
+
* wrapper such as `sudo`. Every type comes from `@eslint-sh/eslint-plugin`:
|
|
10
|
+
* the options and message ids passed to `defineShellRule` flow into
|
|
11
|
+
* `context.options` and `context.report`, and `ShellRuleContext` types the
|
|
12
|
+
* helper that reports outside `create()`.
|
|
13
|
+
*
|
|
14
|
+
* @see ../custom-rule-tutorial.md — the tutorial this rule backs
|
|
15
|
+
*/
|
|
16
|
+
import { defineShellRule } from '@eslint-sh/eslint-plugin';
|
|
17
|
+
import type { ShellCommandSite, ShellRuleContext } from '@eslint-sh/eslint-plugin';
|
|
18
|
+
|
|
19
|
+
/** The rule's one options object; `commands` defaults to `[]`. */
|
|
20
|
+
type Options = [{ commands?: string[] }?];
|
|
21
|
+
|
|
22
|
+
/** Reports `site` when the command it runs, after wrappers, is in `listed`. */
|
|
23
|
+
function reportIfListed(
|
|
24
|
+
context: ShellRuleContext<Options, 'listed'>,
|
|
25
|
+
site: ShellCommandSite,
|
|
26
|
+
listed: ReadonlySet<string>,
|
|
27
|
+
): void {
|
|
28
|
+
const { effective } = site.resolveInvokedCommand();
|
|
29
|
+
if (effective.static && listed.has(effective.text)) {
|
|
30
|
+
context.report({ node: site.node, messageId: 'listed', data: { name: effective.text } });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const noListedCommands = defineShellRule<Options, 'listed'>({
|
|
35
|
+
meta: {
|
|
36
|
+
type: 'problem',
|
|
37
|
+
docs: {
|
|
38
|
+
description: 'disallow the commands named in the `commands` option',
|
|
39
|
+
},
|
|
40
|
+
schema: [
|
|
41
|
+
{
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: { commands: { type: 'array', items: { type: 'string' } } },
|
|
44
|
+
additionalProperties: false,
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
messages: {
|
|
48
|
+
listed: '`{{name}}` is not allowed in this project.',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
create(context) {
|
|
52
|
+
const listed = new Set(context.options[0]?.commands ?? []);
|
|
53
|
+
return {
|
|
54
|
+
File() {
|
|
55
|
+
for (const site of context.sourceCode.getCommandSites()) {
|
|
56
|
+
reportIfListed(context, site, listed);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export default noListedCommands;
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
* The complete "flag `rm -rf`" custom rule from the custom-rule tutorial
|
|
3
3
|
* (`../custom-rule-tutorial.md`). Plain, directly-runnable JavaScript (no
|
|
4
4
|
* compile step) — this file *is* the tutorial's rule-module code fence,
|
|
5
|
-
* copy-paste-verbatim;
|
|
5
|
+
* copy-paste-verbatim; this package's test suite imports this
|
|
6
6
|
* exact file (not a reconstruction) and asserts the markdown fence matches
|
|
7
7
|
* it byte-for-byte, so the two can't silently drift.
|
|
8
8
|
*
|
|
9
|
+
* Built with `defineShellRule`, which declares `meta.languages: ['sh/sh']`
|
|
10
|
+
* for it, so ESLint refuses to run the rule on a file of any other language.
|
|
11
|
+
*
|
|
9
12
|
* Built on the `:command` launch class from the selector cookbook
|
|
10
13
|
* (`../selectors.md`) — `:command` matches every `CallExpr`, so `create()`
|
|
11
14
|
* narrows further by inspecting the command word and its flag arguments,
|
|
@@ -16,12 +19,16 @@
|
|
|
16
19
|
*
|
|
17
20
|
* Flag words are resolved with `@eslint-sh/eslint-plugin`'s view-aware
|
|
18
21
|
* `resolveWord` (backed by `sh-ast/analyze`) rather than a
|
|
19
|
-
* hand-rolled scan: a word is only
|
|
20
|
-
*
|
|
21
|
-
* concatenations)
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* hand-rolled scan: a word is only treated as a short-option cluster
|
|
23
|
+
* when it is *statically* that string (bare or
|
|
24
|
+
* quoted literal concatenations). Recursive (`r`/`R`) and force (`f`)
|
|
25
|
+
* flags accumulate across words before `--`; uppercase `F` is not force.
|
|
26
|
+
* Options are matched anywhere before `--`, GNU-style, even after operands.
|
|
27
|
+
* This teaching example does not model long options or option precedence.
|
|
28
|
+
* A word containing a `ParamExp`/`CmdSubst`/`ArithmExp`/
|
|
29
|
+
* `ProcSubst` resolves to `{ static: false, reason }` (`'expansion'`,
|
|
30
|
+
* `'tilde'`, `'glob'`, …) rather than being silently erased into a static
|
|
31
|
+
* match — see `resolveWord`'s API docs. Importing
|
|
25
32
|
* it from the plugin package (rather than `sh-ast/analyze` directly) means
|
|
26
33
|
* a project that installs only `@eslint-sh/eslint-plugin` has everything
|
|
27
34
|
* this rule needs, with no separate `sh-ast` install or npm-hoisting
|
|
@@ -30,13 +37,13 @@
|
|
|
30
37
|
* @see ../custom-rule-tutorial.md — the tutorial this rule backs
|
|
31
38
|
* @see ../selectors.md — the `:command` launch class
|
|
32
39
|
*/
|
|
33
|
-
import { resolveWord } from '@eslint-sh/eslint-plugin';
|
|
40
|
+
import { defineShellRule, resolveWord } from '@eslint-sh/eslint-plugin';
|
|
34
41
|
|
|
35
|
-
const noRmRf = {
|
|
42
|
+
const noRmRf = defineShellRule({
|
|
36
43
|
meta: {
|
|
37
44
|
type: 'problem',
|
|
38
45
|
docs: {
|
|
39
|
-
description: 'disallow
|
|
46
|
+
description: 'disallow recursive-force `rm` invocations',
|
|
40
47
|
},
|
|
41
48
|
schema: [],
|
|
42
49
|
messages: {
|
|
@@ -62,11 +69,15 @@ const noRmRf = {
|
|
|
62
69
|
const endOfOptions = words.indexOf('--');
|
|
63
70
|
const flagCandidates = words.slice(1, endOfOptions === -1 ? undefined : endOfOptions);
|
|
64
71
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
// Only static short-option clusters contribute flags; operands,
|
|
73
|
+
// long options, and unresolved words supply no evidence. Recursive
|
|
74
|
+
// and force may occur in the same word or in separate words.
|
|
75
|
+
const shortOptions = flagCandidates.filter(
|
|
76
|
+
(word) => word !== null && /^-[a-zA-Z]+$/.test(word),
|
|
69
77
|
);
|
|
78
|
+
const hasRecursive = shortOptions.some((word) => /[rR]/.test(word));
|
|
79
|
+
const hasForce = shortOptions.some((word) => word.includes('f'));
|
|
80
|
+
const isRecursiveForce = hasRecursive && hasForce;
|
|
70
81
|
|
|
71
82
|
if (isRecursiveForce) {
|
|
72
83
|
context.report({ node, loc: node.loc, messageId: 'rmRf' });
|
|
@@ -74,6 +85,6 @@ const noRmRf = {
|
|
|
74
85
|
},
|
|
75
86
|
};
|
|
76
87
|
},
|
|
77
|
-
};
|
|
88
|
+
});
|
|
78
89
|
|
|
79
90
|
export default noRmRf;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TypeScript example rule from "Reading nodes" (`../reading-nodes.md`).
|
|
3
|
+
* This package's test suite asserts the page's code fence matches this
|
|
4
|
+
* file byte-for-byte, type-checks it against the package's published
|
|
5
|
+
* declarations, and runs it, with its fix, through a real `eslint` `Linter`.
|
|
6
|
+
*
|
|
7
|
+
* It reports an `rm` argument that is a lone, unquoted variable (`rm -rf $dir`)
|
|
8
|
+
* and quotes it. Every field it reads is typed by `ShellNode`: narrowing on
|
|
9
|
+
* `type` is the only thing between a listener's node and its fields.
|
|
10
|
+
*
|
|
11
|
+
* @see ../reading-nodes.md — the page this rule backs
|
|
12
|
+
*/
|
|
13
|
+
import { defineShellRule } from '@eslint-sh/eslint-plugin';
|
|
14
|
+
|
|
15
|
+
const noUnquotedRmVariable = defineShellRule({
|
|
16
|
+
meta: {
|
|
17
|
+
type: 'problem',
|
|
18
|
+
fixable: 'code',
|
|
19
|
+
docs: { description: 'require the variable operands of `rm` to be quoted' },
|
|
20
|
+
schema: [],
|
|
21
|
+
messages: {
|
|
22
|
+
unquoted: 'Quote `${{name}}`: unquoted, it is split and globbed before `rm` runs.',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
create(context) {
|
|
26
|
+
return {
|
|
27
|
+
CallExpr(node) {
|
|
28
|
+
// A listener keyed by a node type receives that node type: `args`, `assigns`, … are typed.
|
|
29
|
+
if (context.sourceCode.getCommandName(node) !== 'rm') return;
|
|
30
|
+
|
|
31
|
+
// `args` is always an array of `Word`s (the command word first), never null.
|
|
32
|
+
for (const word of node.args.slice(1)) {
|
|
33
|
+
// A `Word` is a list of parts; a lone `$name` is a single `ParamExp` part.
|
|
34
|
+
const [part] = word.parts;
|
|
35
|
+
if (word.parts.length !== 1 || part.type !== 'ParamExp') continue;
|
|
36
|
+
|
|
37
|
+
context.report({
|
|
38
|
+
node: word,
|
|
39
|
+
messageId: 'unquoted',
|
|
40
|
+
// `param` is optional: a `ParamExp` with no name (rare) has no `param` child.
|
|
41
|
+
data: { name: part.param?.value ?? '' },
|
|
42
|
+
// `word.range` is the tuple ESLint's fixer methods take, so it needs no cast.
|
|
43
|
+
fix: (fixer) => [
|
|
44
|
+
fixer.insertTextBeforeRange(word.range, '"'),
|
|
45
|
+
fixer.insertTextAfterRange(word.range, '"'),
|
|
46
|
+
],
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export default noUnquotedRmVariable;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A declaration convention whose declarations live beside each script:
|
|
3
|
+
* `deploy.sh` declares its commands in `deploy.commands`, one name per line.
|
|
4
|
+
* It reads the sidecar while linting. From `../declaration-conventions.md`,
|
|
5
|
+
* and run in CI.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
import { basename, dirname, extname, join, relative } from 'node:path';
|
|
9
|
+
|
|
10
|
+
/** `deploy.sh` → `deploy.commands`, next to the script on disk. */
|
|
11
|
+
export function sidecarPathFor(script) {
|
|
12
|
+
return join(dirname(script), `${basename(script, extname(script))}.commands`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** One file declaration per non-blank, non-`#` line of `text`. */
|
|
16
|
+
export function parseSidecar(text, shownPath) {
|
|
17
|
+
return text.split(/\r?\n/u).flatMap((line, index) => {
|
|
18
|
+
const name = line.trim();
|
|
19
|
+
if (name === '' || name.startsWith('#')) return [];
|
|
20
|
+
return [{ kind: 'file', name, path: shownPath, line: index + 1 }];
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Reads the linted script's own sidecar each time it is asked. */
|
|
25
|
+
const sidecarCommands = {
|
|
26
|
+
declaredCommands(sourceCode, file) {
|
|
27
|
+
const path = sidecarPathFor(file.physicalFilename);
|
|
28
|
+
let text;
|
|
29
|
+
try {
|
|
30
|
+
text = readFileSync(path, 'utf8');
|
|
31
|
+
} catch {
|
|
32
|
+
return []; // No sidecar: the script declares nothing.
|
|
33
|
+
}
|
|
34
|
+
return parseSidecar(text, relative(file.cwd, path));
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default sidecarCommands;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sidecar convention, reading every sidecar under `root` once, when the
|
|
3
|
+
* ESLint config loads, and keeping their text as plain data, which ESLint's
|
|
4
|
+
* `--cache` sees. From `../declaration-conventions.md`, and run in CI.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
7
|
+
import { join, relative } from 'node:path';
|
|
8
|
+
import { parseSidecar, sidecarPathFor } from './sidecar-convention.mjs';
|
|
9
|
+
|
|
10
|
+
/** A convention over the `*.commands` files under `root`, as they are now. */
|
|
11
|
+
export function sidecarSnapshot(root) {
|
|
12
|
+
const sidecars = {};
|
|
13
|
+
for (const entry of readdirSync(root, { recursive: true })) {
|
|
14
|
+
if (entry.endsWith('.commands')) {
|
|
15
|
+
sidecars[entry] = readFileSync(join(root, entry), 'utf8');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
sidecars, // data, not a closure: part of the config ESLint's cache hashes
|
|
20
|
+
declaredCommands(sourceCode, file) {
|
|
21
|
+
const path = sidecarPathFor(file.physicalFilename);
|
|
22
|
+
const text = sidecars[relative(root, path)];
|
|
23
|
+
return text === undefined ? [] : parseSidecar(text, relative(file.cwd, path));
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A rule of your own that reads the bound declaration convention: it reports
|
|
3
|
+
* a declaration that is out of alphabetical order. From
|
|
4
|
+
* `../declaration-conventions.md`, and run in CI.
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
defineShellRule,
|
|
8
|
+
readDeclaredCommands,
|
|
9
|
+
resolveDeclarationConvention,
|
|
10
|
+
} from '@eslint-sh/eslint-plugin';
|
|
11
|
+
|
|
12
|
+
const sortedDeclarations = defineShellRule({
|
|
13
|
+
meta: {
|
|
14
|
+
type: 'suggestion',
|
|
15
|
+
schema: [],
|
|
16
|
+
messages: { unsorted: 'Declare `{{name}}` before `{{previous}}`.' },
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
// The bound convention, checked exactly as the built-in rules check it;
|
|
20
|
+
// `null` when none is bound, and then the rule has nothing to check.
|
|
21
|
+
const convention = resolveDeclarationConvention(context);
|
|
22
|
+
if (convention === null) {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
File() {
|
|
27
|
+
const { sourceCode } = context;
|
|
28
|
+
// The file's declarations, checked as the built-in rules check them.
|
|
29
|
+
// Only those written in the script have a range to report at.
|
|
30
|
+
const declared = readDeclaredCommands(convention, context).filter(
|
|
31
|
+
(each) => each.kind === 'script',
|
|
32
|
+
);
|
|
33
|
+
for (let i = 1; i < declared.length; i++) {
|
|
34
|
+
const previous = declared[i - 1].name;
|
|
35
|
+
const { name, range } = declared[i];
|
|
36
|
+
if (name < previous) {
|
|
37
|
+
context.report({
|
|
38
|
+
loc: {
|
|
39
|
+
start: sourceCode.getLocFromIndex(range[0]),
|
|
40
|
+
end: sourceCode.getLocFromIndex(range[1]),
|
|
41
|
+
},
|
|
42
|
+
messageId: 'unsorted',
|
|
43
|
+
data: { name, previous },
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export default sortedDeclarations;
|