@eslint-sh/eslint-plugin 0.3.0 → 0.5.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 +10 -6
- package/dist/eslint-plugin.d.ts +91 -68
- package/dist/index.d.ts +12 -26
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -26
- package/dist/index.js.map +1 -1
- package/dist/language/selector-classes.d.ts +6 -10
- package/dist/language/selector-classes.d.ts.map +1 -1
- package/dist/language/selector-classes.js +6 -9
- package/dist/language/selector-classes.js.map +1 -1
- package/dist/language/shell-language-options.d.ts +9 -11
- package/dist/language/shell-language-options.d.ts.map +1 -1
- package/dist/language/shell-language-options.js +7 -11
- package/dist/language/shell-language-options.js.map +1 -1
- package/dist/language/shell-language.d.ts +10 -12
- package/dist/language/shell-language.d.ts.map +1 -1
- package/dist/language/shell-language.js +45 -79
- package/dist/language/shell-language.js.map +1 -1
- package/dist/language/shell-source-code.d.ts +10 -19
- package/dist/language/shell-source-code.d.ts.map +1 -1
- package/dist/language/shell-source-code.js +165 -62
- package/dist/language/shell-source-code.js.map +1 -1
- package/dist/language/source-coordinates.d.ts +23 -0
- package/dist/language/source-coordinates.d.ts.map +1 -0
- package/dist/language/source-coordinates.js +58 -0
- package/dist/language/source-coordinates.js.map +1 -0
- package/dist/language/syntax-view.d.ts +60 -0
- package/dist/language/syntax-view.d.ts.map +1 -0
- package/dist/language/syntax-view.js +164 -0
- package/dist/language/syntax-view.js.map +1 -0
- package/dist/language/word-resolution.d.ts +11 -0
- package/dist/language/word-resolution.d.ts.map +1 -0
- package/dist/language/word-resolution.js +18 -0
- package/dist/language/word-resolution.js.map +1 -0
- package/docs/custom-rule-tutorial.md +4 -4
- package/docs/examples/no-rm-rf-rule.mjs +2 -2
- package/docs/selectors.md +9 -0
- package/package.json +9 -3
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projects faithful upstream syntax into the source view ESLint's selectors and
|
|
3
|
+
* fixers consume. Lowercase fields, UTF-16 spans, and synthetic auxiliary views
|
|
4
|
+
* are consumer conventions; the original syntax remains untouched and associated
|
|
5
|
+
* by identity. Bounds are observations of the original parse, tied to this exact
|
|
6
|
+
* source. This is neither upstream Walk nor a replacement syntax model.
|
|
7
|
+
*/
|
|
8
|
+
import { SYNTAX_INTERFACES, SYNTAX_SCHEMA } from 'sh-ast';
|
|
9
|
+
import { SourceCoordinates } from './source-coordinates.js';
|
|
10
|
+
/** Raw syntax is associated without adding parent cycles or consumer fields to its records. */
|
|
11
|
+
const originalSyntax = new WeakMap();
|
|
12
|
+
/** The upstream Node union distinguishes real nodes from auxiliary syntax records. */
|
|
13
|
+
const nodeTypes = new Set(SYNTAX_INTERFACES.Node);
|
|
14
|
+
/** Parser input is a JavaScript string; unexpected invalid-byte fields must never be silently replaced. */
|
|
15
|
+
const textDecoder = new TextDecoder('utf-8', { fatal: true });
|
|
16
|
+
/** ESLint child relationships follow the consumer projection, including auxiliary views; not upstream Walk ordering. @internal */
|
|
17
|
+
export const shellVisitorKeys = Object.freeze(Object.fromEntries(Object.entries(SYNTAX_SCHEMA).map(([name, schema]) => [
|
|
18
|
+
name,
|
|
19
|
+
Object.freeze(Object.entries(schema.fields)
|
|
20
|
+
.filter(([, field]) => field.kind === 'struct' || field.kind === 'interface')
|
|
21
|
+
.map(([fieldName]) => fieldName.toLowerCase())),
|
|
22
|
+
])));
|
|
23
|
+
/** Checks record identity without pretending unknown transport values already have syntax types. */
|
|
24
|
+
function isRecord(value) {
|
|
25
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
/** Parser-produced records with an upstream Node discriminator may use the faithful bounds operations. */
|
|
28
|
+
function isSyntaxNode(value) {
|
|
29
|
+
return isRecord(value) && typeof value.Type === 'string' && nodeTypes.has(value.Type);
|
|
30
|
+
}
|
|
31
|
+
/** Identifies projected children without confusing raw position records or byte strings with nodes. @internal */
|
|
32
|
+
export function isShellNode(value) {
|
|
33
|
+
return isRecord(value) && typeof value.type === 'string' && Array.isArray(value.range);
|
|
34
|
+
}
|
|
35
|
+
/** Directive processing requires both the comment kind and its decoded text. @internal */
|
|
36
|
+
export function isShellComment(node) {
|
|
37
|
+
return node.type === 'Comment' && typeof node.text === 'string';
|
|
38
|
+
}
|
|
39
|
+
/** Recognizes the guaranteed root shape after projection without changing its associated identity. */
|
|
40
|
+
function isShellFile(node) {
|
|
41
|
+
return node.type === 'File' && Array.isArray(node.stmts) && node.stmts.every(isShellNode);
|
|
42
|
+
}
|
|
43
|
+
/** Returns the actual parser node, or undefined for consumer-created/synthetic views. @internal */
|
|
44
|
+
export function syntaxForNode(node) {
|
|
45
|
+
return originalSyntax.get(node);
|
|
46
|
+
}
|
|
47
|
+
/** Converts valid source-associated Go strings; invalid byte sequences have no faithful text view here. */
|
|
48
|
+
function sourceText(value) {
|
|
49
|
+
if (typeof value === 'string') {
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
if (value instanceof Uint8Array) {
|
|
53
|
+
return textDecoder.decode(value);
|
|
54
|
+
}
|
|
55
|
+
throw new TypeError('shell text view expected a Go string');
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Builds a source-bound view immediately from one parse's File and Positions.
|
|
59
|
+
* Snapshot bounds describe that original parse, never subsequent syntax edits.
|
|
60
|
+
* A missing observation indicates a mismatched/incomplete snapshot and cannot be
|
|
61
|
+
* repaired by querying live syntax without reintroducing repeated subtree work.
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
export function adaptSyntaxFile(file, source, positions) {
|
|
65
|
+
if (!isRecord(positions) || typeof positions.get !== 'function') {
|
|
66
|
+
throw new TypeError('shell view requires a source position snapshot');
|
|
67
|
+
}
|
|
68
|
+
const coordinates = new SourceCoordinates(source);
|
|
69
|
+
const wholeSource = { start: coordinates.atIndex(0), end: coordinates.atIndex(source.length) };
|
|
70
|
+
/** Invalid/recovered positions have no exact text boundary; the enclosing view supplies an explicit consumer fallback. */
|
|
71
|
+
const position = (raw, fallback) => raw.IsValid && !raw.IsRecovered ? coordinates.fromByteOffset(raw.Offset) : fallback;
|
|
72
|
+
/** Each projected record owns its derived fields, while real syntax nodes retain their original identity. */
|
|
73
|
+
const project = (raw, fallback) => {
|
|
74
|
+
if (!isRecord(raw) || typeof raw.Type !== 'string' || !Object.hasOwn(SYNTAX_SCHEMA, raw.Type)) {
|
|
75
|
+
throw new TypeError('shell view expected a known upstream syntax record');
|
|
76
|
+
}
|
|
77
|
+
const schema = SYNTAX_SCHEMA[raw.Type];
|
|
78
|
+
const realNode = isSyntaxNode(raw);
|
|
79
|
+
// eslint-disable-next-line @api-extractor-tools/forgotten-export -- This span belongs only to the private projection closure, not the exported signature.
|
|
80
|
+
let span = fallback;
|
|
81
|
+
if (realNode) {
|
|
82
|
+
const observed = positions.get(raw);
|
|
83
|
+
if (observed === undefined) {
|
|
84
|
+
throw new TypeError(`shell position snapshot has no observation for ${raw.Type}`);
|
|
85
|
+
}
|
|
86
|
+
span = {
|
|
87
|
+
start: position(observed.Pos, fallback.start),
|
|
88
|
+
end: position(observed.End, fallback.end),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const fields = {};
|
|
92
|
+
const children = [];
|
|
93
|
+
for (const [name, field] of Object.entries(schema.fields)) {
|
|
94
|
+
if (field.kind === 'position') {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const value = raw[name];
|
|
98
|
+
const key = name.toLowerCase();
|
|
99
|
+
if (field.kind === 'struct' || field.kind === 'interface') {
|
|
100
|
+
if (field.slice) {
|
|
101
|
+
const items = value === null ? [] : value;
|
|
102
|
+
if (!Array.isArray(items)) {
|
|
103
|
+
throw new TypeError(`shell view expected ${raw.Type}.${name} to be a slice`);
|
|
104
|
+
}
|
|
105
|
+
const projected = items
|
|
106
|
+
.filter((item) => item !== null)
|
|
107
|
+
.map((item) => project(item, span));
|
|
108
|
+
fields[key] = projected;
|
|
109
|
+
children.push(...projected);
|
|
110
|
+
}
|
|
111
|
+
else if (value !== null) {
|
|
112
|
+
const child = project(value, span);
|
|
113
|
+
fields[key] = child;
|
|
114
|
+
children.push(child);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
fields[key] = field.kind === 'string' ? sourceText(value) : value;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (!realNode && children.length > 0) {
|
|
122
|
+
span = {
|
|
123
|
+
start: coordinates.atIndex(Math.min(...children.map((node) => node.range[0]))),
|
|
124
|
+
end: coordinates.atIndex(Math.max(...children.map((node) => node.range[1]))),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const view = {
|
|
128
|
+
...fields,
|
|
129
|
+
type: raw.Type,
|
|
130
|
+
range: [span.start.index, span.end.index],
|
|
131
|
+
loc: {
|
|
132
|
+
start: { line: span.start.line, column: span.start.column },
|
|
133
|
+
end: { line: span.end.line, column: span.end.column },
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
if (realNode) {
|
|
137
|
+
originalSyntax.set(view, raw);
|
|
138
|
+
}
|
|
139
|
+
return view;
|
|
140
|
+
};
|
|
141
|
+
const root = project(file, wholeSource);
|
|
142
|
+
if (!isShellFile(root)) {
|
|
143
|
+
throw new TypeError('shell view expected a File with statements');
|
|
144
|
+
}
|
|
145
|
+
return root;
|
|
146
|
+
}
|
|
147
|
+
/** Consumer traversal visits each projected child, including auxiliary views, without claiming upstream Walk control semantics. @internal */
|
|
148
|
+
export function walkShell(root, visit) {
|
|
149
|
+
visit(root);
|
|
150
|
+
for (const key of shellVisitorKeys[root.type] ?? []) {
|
|
151
|
+
const value = root[key];
|
|
152
|
+
if (Array.isArray(value)) {
|
|
153
|
+
for (const child of value) {
|
|
154
|
+
if (isShellNode(child)) {
|
|
155
|
+
walkShell(child, visit);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else if (isShellNode(value)) {
|
|
160
|
+
walkShell(value, visit);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=syntax-view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syntax-view.js","sourceRoot":"","sources":["../../src/language/syntax-view.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAE1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AA4C5D,+FAA+F;AAC/F,MAAM,cAAc,GAAG,IAAI,OAAO,EAAyB,CAAC;AAC5D,sFAAsF;AACtF,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;AAClD,2GAA2G;AAC3G,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AAE9D,kIAAkI;AAClI,MAAM,CAAC,MAAM,gBAAgB,GAAgD,MAAM,CAAC,MAAM,CACxF,MAAM,CAAC,WAAW,CAChB,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC;IACpD,IAAI;IACJ,MAAM,CAAC,MAAM,CACX,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC;SAC1B,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,CAAC;SAC5E,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CACjD;CACF,CAAC,CACH,CACF,CAAC;AAEF,oGAAoG;AACpG,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,0GAA0G;AAC1G,SAAS,YAAY,CAAC,KAAc;IAClC,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACxF,CAAC;AAED,iHAAiH;AACjH,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AACzF,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,cAAc,CAAC,IAAe;IAC5C,OAAO,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC;AAClE,CAAC;AAED,sGAAsG;AACtG,SAAS,WAAW,CAAC,IAAe;IAClC,OAAO,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;AAC5F,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,aAAa,CAAC,IAAe;IAC3C,OAAO,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC;AAED,2GAA2G;AAC3G,SAAS,UAAU,CAAC,KAAc;IAChC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,KAAK,YAAY,UAAU,EAAE,CAAC;QAChC,OAAO,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IACD,MAAM,IAAI,SAAS,CAAC,sCAAsC,CAAC,CAAC;AAC9D,CAAC;AAQD;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAU,EACV,MAAc,EACd,SAA2B;IAE3B,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,OAAO,SAAS,CAAC,GAAG,KAAK,UAAU,EAAE,CAAC;QAChE,MAAM,IAAI,SAAS,CAAC,gDAAgD,CAAC,CAAC;IACxE,CAAC;IACD,MAAM,WAAW,GAAG,IAAI,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,WAAW,GAAG,EAAE,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;IAE/F,0HAA0H;IAC1H,MAAM,QAAQ,GAAG,CAAC,GAAwB,EAAE,QAA2B,EAAqB,EAAE,CAC5F,GAAG,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAEtF,6GAA6G;IAC7G,MAAM,OAAO,GAAG,CAAC,GAAY,EAAE,QAAmB,EAAa,EAAE;QAC/D,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9F,MAAM,IAAI,SAAS,CAAC,oDAAoD,CAAC,CAAC;QAC5E,CAAC;QACD,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QACnC,0JAA0J;QAC1J,IAAI,IAAI,GAAc,QAAQ,CAAC;QAC/B,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACpC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3B,MAAM,IAAI,SAAS,CAAC,kDAAkD,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YACpF,CAAC;YACD,IAAI,GAAG;gBACL,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC;gBAC7C,GAAG,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC;aAC1C,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAA4B,EAAE,CAAC;QAC3C,MAAM,QAAQ,GAAgB,EAAE,CAAC;QACjC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1D,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;gBAC9B,SAAS;YACX,CAAC;YACD,MAAM,KAAK,GAAY,GAAG,CAAC,IAAI,CAAC,CAAC;YACjC,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YAC/B,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBAC1D,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;oBAChB,MAAM,KAAK,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;oBAC1C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;wBAC1B,MAAM,IAAI,SAAS,CAAC,uBAAuB,GAAG,CAAC,IAAI,IAAI,IAAI,gBAAgB,CAAC,CAAC;oBAC/E,CAAC;oBACD,MAAM,SAAS,GAAG,KAAK;yBACpB,MAAM,CAAC,CAAC,IAAa,EAAE,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC;yBACxC,GAAG,CAAC,CAAC,IAAa,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;oBAC/C,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;oBACxB,QAAQ,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC;gBAC9B,CAAC;qBAAM,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;oBAC1B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;oBACnC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;oBACpB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACvB,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;YACpE,CAAC;QACH,CAAC;QACD,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrC,IAAI,GAAG;gBACL,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC9E,GAAG,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;aAC7E,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAc;YACtB,GAAG,MAAM;YACT,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,KAAK,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;YACzC,GAAG,EAAE;gBACH,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;gBAC3D,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE;aACtD;SACF,CAAC;QACF,IAAI,QAAQ,EAAE,CAAC;YACb,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IACxC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,SAAS,CAAC,4CAA4C,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,6IAA6I;AAC7I,MAAM,UAAU,SAAS,CAAC,IAAe,EAAE,KAAgC;IACzE,KAAK,CAAC,IAAI,CAAC,CAAC;IACZ,KAAK,MAAM,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QACpD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;QACxB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;gBAC1B,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;oBACvB,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;gBAC1B,CAAC;YACH,CAAC;QACH,CAAC;aAAM,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;YAC9B,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ResolveWordOptions, WordResolution } from 'sh-ast/analyze';
|
|
2
|
+
import type { ShellNode } from './syntax-view.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves a parsed lint Word using the upstream-shaped syntax associated with it.
|
|
5
|
+
* The view supplies locations and selector fields; sh-ast/analyze owns the word
|
|
6
|
+
* interpretation. Fabricated or synthetic view nodes have no source syntax and
|
|
7
|
+
* are rejected instead of being treated as known or unknown shell values.
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveWord(word: ShellNode, options?: ResolveWordOptions): WordResolution;
|
|
11
|
+
//# sourceMappingURL=word-resolution.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"word-resolution.d.ts","sourceRoot":"","sources":["../../src/language/word-resolution.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,cAAc,CAMzF"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Bridges custom-rule analysis from ESLint view identity back to faithful syntax. */
|
|
2
|
+
import { resolveWord as resolveSyntaxWord } from 'sh-ast/analyze';
|
|
3
|
+
import { syntaxForNode } from './syntax-view.js';
|
|
4
|
+
/**
|
|
5
|
+
* Resolves a parsed lint Word using the upstream-shaped syntax associated with it.
|
|
6
|
+
* The view supplies locations and selector fields; sh-ast/analyze owns the word
|
|
7
|
+
* interpretation. Fabricated or synthetic view nodes have no source syntax and
|
|
8
|
+
* are rejected instead of being treated as known or unknown shell values.
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export function resolveWord(word, options) {
|
|
12
|
+
const syntax = syntaxForNode(word);
|
|
13
|
+
if (syntax?.Type !== 'Word') {
|
|
14
|
+
throw new TypeError('resolveWord expects a parsed shell Word from this language adapter');
|
|
15
|
+
}
|
|
16
|
+
return resolveSyntaxWord(syntax, options);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=word-resolution.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"word-resolution.js","sourceRoot":"","sources":["../../src/language/word-resolution.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,OAAO,EAAE,WAAW,IAAI,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAElE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGjD;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,IAAe,EAAE,OAA4B;IACvE,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,MAAM,EAAE,IAAI,KAAK,MAAM,EAAE,CAAC;QAC5B,MAAM,IAAI,SAAS,CAAC,oEAAoE,CAAC,CAAC;IAC5F,CAAC;IACD,OAAO,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC5C,CAAC"}
|
|
@@ -46,8 +46,8 @@ flag?) the same way a hand-written rule narrows any other selector-first ESLint
|
|
|
46
46
|
* here the selector does the node-type narrowing, `create()` only does the
|
|
47
47
|
* domain-specific check).
|
|
48
48
|
*
|
|
49
|
-
* Flag words are resolved with `@eslint-sh/eslint-plugin`'s
|
|
50
|
-
* `resolveWord` (
|
|
49
|
+
* Flag words are resolved with `@eslint-sh/eslint-plugin`'s view-aware
|
|
50
|
+
* `resolveWord` (backed by `sh-ast/analyze`) rather than a
|
|
51
51
|
* hand-rolled scan: a word is only ever treated as the literal text
|
|
52
52
|
* `-rf`/`-fr` when it is *statically* that string (bare or quoted literal
|
|
53
53
|
* concatenations); a word containing a `ParamExp`/`CmdSubst`/`ArithmExp`/
|
|
@@ -117,8 +117,8 @@ A few things worth calling out:
|
|
|
117
117
|
classes](./selectors.md#the-six-launch-classes) — not a bare node-type name. ESLint's
|
|
118
118
|
`esquery`-based rule-listener matching (via `ShellLanguage.matchesSelectorClass`) treats it
|
|
119
119
|
exactly like any other selector key such as `CallExpr` or `CallExpr[args.0.parts.0.value='jq']`.
|
|
120
|
-
- `resolveWord(arg, { context: 'command-argument' })` —
|
|
121
|
-
(
|
|
120
|
+
- `resolveWord(arg, { context: 'command-argument' })` — exported from `@eslint-sh/eslint-plugin`
|
|
121
|
+
(using the raw Word associated with the lint view) — is the correct way to ask "is this word statically the string X":
|
|
122
122
|
it returns `{ static: true, text }` for a bare or
|
|
123
123
|
quoted literal (`rm`, `'r'm`, `"-rf"` all resolve), and `{ static: false, reason }` the moment any
|
|
124
124
|
part of the word depends on a `ParamExp`/`CmdSubst`/`ArithmExp`/`ProcSubst` — never inventing a
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* here the selector does the node-type narrowing, `create()` only does the
|
|
15
15
|
* domain-specific check).
|
|
16
16
|
*
|
|
17
|
-
* Flag words are resolved with `@eslint-sh/eslint-plugin`'s
|
|
18
|
-
* `resolveWord` (
|
|
17
|
+
* Flag words are resolved with `@eslint-sh/eslint-plugin`'s view-aware
|
|
18
|
+
* `resolveWord` (backed by `sh-ast/analyze`) rather than a
|
|
19
19
|
* hand-rolled scan: a word is only ever treated as the literal text
|
|
20
20
|
* `-rf`/`-fr` when it is *statically* that string (bare or quoted literal
|
|
21
21
|
* concatenations); a word containing a `ParamExp`/`CmdSubst`/`ArithmExp`/
|
package/docs/selectors.md
CHANGED
|
@@ -27,6 +27,15 @@ Looking for a complete, runnable `create()` that uses one of these selectors —
|
|
|
27
27
|
C-style arithmetic header (`init`/`cond`/`post`), not a loop statement in its own right. A
|
|
28
28
|
`for ((i=0;i<3;i++))` loop's outer node is still `ForClause`, so `:loop` already reaches it.
|
|
29
29
|
|
|
30
|
+
## Node-type names vs. TypeScript interface names
|
|
31
|
+
|
|
32
|
+
Selectors and `node.type` use the **bare** node-type name shown in the table above and
|
|
33
|
+
throughout this cookbook (`CallExpr`, `IfClause`, `FuncDecl`, ...). The plugin's `ShellNode`
|
|
34
|
+
view preserves these discriminator values while supplying lowercase child fields, UTF-16
|
|
35
|
+
ranges, and locations. The underlying `sh-ast` syntax uses its own `Type` discriminator and
|
|
36
|
+
upstream field spellings; rule listeners receive the plugin view. Import view types from
|
|
37
|
+
`@eslint-sh/eslint-plugin` (or `eslint-sh`) when typing a custom rule.
|
|
38
|
+
|
|
30
39
|
## Recipes
|
|
31
40
|
|
|
32
41
|
### Match a specific command by name
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eslint-sh/eslint-plugin",
|
|
3
3
|
"description": "Shell script parsing and syntax-error reporting for ESLint, with custom-rule authoring over a typed shell AST, backed by sh-ast.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"author": "Mike North <michael.l.north@gmail.com>",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@eslint/core": "^1.2.1",
|
|
8
8
|
"@eslint/plugin-kit": "^0.7.2",
|
|
9
|
-
"sh-ast": "^0.
|
|
9
|
+
"sh-ast": "^0.5.0"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"eslint": "^10.7.0"
|
|
@@ -65,6 +65,11 @@
|
|
|
65
65
|
},
|
|
66
66
|
"type": "module",
|
|
67
67
|
"types": "./dist/eslint-plugin.d.ts",
|
|
68
|
+
"repository": {
|
|
69
|
+
"type": "git",
|
|
70
|
+
"url": "git+https://github.com/mike-north/eslint-sh.git",
|
|
71
|
+
"directory": "packages/eslint-plugin"
|
|
72
|
+
},
|
|
68
73
|
"scripts": {
|
|
69
74
|
"build": "tsc -p tsconfig.json",
|
|
70
75
|
"check": "concurrently -n lint,types,api-report \"pnpm run check:lint-ts\" \"pnpm run check:typecheck\" \"pnpm run check:api-report\"",
|
|
@@ -75,6 +80,7 @@
|
|
|
75
80
|
"fix:api-report": "api-extractor run --local",
|
|
76
81
|
"fix:lint-ts": "eslint . --fix",
|
|
77
82
|
"test": "vitest run",
|
|
78
|
-
"uplevel-api-reports": "mkdir -p ../../api-report-combined ../../api-docs-temp && cp api-report/eslint-plugin.api.md ../../api-report-combined/ && cp temp/eslint-plugin.api.json ../../api-docs-temp/"
|
|
83
|
+
"uplevel-api-reports": "mkdir -p ../../api-report-combined ../../api-docs-temp && cp api-report/eslint-plugin.api.md ../../api-report-combined/ && cp temp/eslint-plugin.api.json ../../api-docs-temp/",
|
|
84
|
+
"test:candidate": "node --experimental-strip-types test/candidate/run.mjs"
|
|
79
85
|
}
|
|
80
86
|
}
|