@eagleoutice/eslint-config-flowr 2.1.1 → 2.1.3
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 +17 -2
- package/package.json +1 -1
- package/patterns.d.ts +2 -1
- package/patterns.js +150 -25
- package/rules/util.d.ts +3 -3
- package/rules/util.js +4 -7
- package/tsdoc.json +0 -4
package/README.md
CHANGED
|
@@ -75,6 +75,22 @@ To add one, append to [`patterns.ts`](patterns.ts):
|
|
|
75
75
|
| `fix` | `true` always fixes, `false` always suggests, which is right when the replacement can change type narrowing. Omitted, it fixes when the replacement is in scope |
|
|
76
76
|
| `id` | names the pattern in `@lintIgnore` and in the message |
|
|
77
77
|
|
|
78
|
+
The shipped set groups into families:
|
|
79
|
+
|
|
80
|
+
| family | shape it finds | helper it points at |
|
|
81
|
+
| :-- | :-- | :-- |
|
|
82
|
+
| `edge-*` | `e.types === T`, `(e.types & T) !== 0`, and the bitmask spellings around them | `DfEdge` |
|
|
83
|
+
| `vertex-is*`, `vertex-has-origin` | `v.tag === VertexType.X`, `FunctionCallVertex.is(v) && v.origin.includes(o)` | the `*Vertex` objects |
|
|
84
|
+
| `node-is*` | `n.type === RType.X`, negated and through `?.` | `RSymbol`, `RFunctionCall`, and the rest of the `R*` node objects |
|
|
85
|
+
| `argument-is-*`, `call-is-*`, `list-is-implicit` | a guard written out, `RFunctionCall.is(n) && n.named` | the narrower guard of the same object |
|
|
86
|
+
| `symbol-name-comparison*` | `symbol.content === 'name'`, which misses `pkg::name` because an identifier with a namespace is an array | `Identifier.getName` / `Identifier.matches` |
|
|
87
|
+
| `graph-edges-no-alloc`, `some-instead-of-filter-length`, `array-sum` | an allocation or a full walk where a shared constant or a helper does it once | `NoEdges`, `some`, `arraySum` |
|
|
88
|
+
|
|
89
|
+
A pattern is exempt in the file `declaredIn` resolves to, which is why a shape only earns a place here when the
|
|
90
|
+
match names a flowR symbol: without one the rule cannot tell the helper's own body apart from a call site, and
|
|
91
|
+
`@lintIgnore` on the declaration would be papering over that. A guard that *is* the raw comparison
|
|
92
|
+
(`RSymbol.is` compares `type` against `RType.Symbol`) is the case the tag is actually for.
|
|
93
|
+
|
|
78
94
|
An own `patterns` array replaces the defaults rather than merging with them. To propose one, open an issue with the
|
|
79
95
|
[replacement pattern template](.github/ISSUE_TEMPLATE/replacement-pattern.yaml).
|
|
80
96
|
|
|
@@ -83,8 +99,7 @@ An own `patterns` array replaces the defaults rather than merging with them. To
|
|
|
83
99
|
| | silences |
|
|
84
100
|
| :-- | :-- |
|
|
85
101
|
| `// eslint-disable-next-line` | the next line, as usual |
|
|
86
|
-
| `@
|
|
87
|
-
| `@lintIgnore <ids>` | the named rules or pattern ids, all of them when given none |
|
|
102
|
+
| `@lintIgnore <ids>` | the named rules or pattern ids, all of them when given none (put the reason in the prose above it) |
|
|
88
103
|
|
|
89
104
|
A tag on a declaration covers everything below it, a header comment above the imports covers the file. The lookup is
|
|
90
105
|
guarded by a single string search, so files without either tag pay nothing.
|
package/package.json
CHANGED
package/patterns.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Default patterns of `flowr/replacement-pattern`, see the README for the format.
|
|
3
3
|
* The helpers they point at live in flowR: `DfEdge` in `dataflow/graph/edge`, the vertex helpers in
|
|
4
|
-
* `dataflow/graph/vertex`,
|
|
4
|
+
* `dataflow/graph/vertex`, `NoEdges` in `dataflow/graph/graph`, the `RNode` helpers in
|
|
5
|
+
* `r-bridge/lang-4.x/ast/model`, and the collection helpers in `util/collections`.
|
|
5
6
|
*/
|
|
6
7
|
import type { ReplacementPattern } from './pattern-type';
|
|
7
8
|
declare const patterns: readonly ReplacementPattern[];
|
package/patterns.js
CHANGED
|
@@ -2,16 +2,22 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Default patterns of `flowr/replacement-pattern`, see the README for the format.
|
|
4
4
|
* The helpers they point at live in flowR: `DfEdge` in `dataflow/graph/edge`, the vertex helpers in
|
|
5
|
-
* `dataflow/graph/vertex`,
|
|
5
|
+
* `dataflow/graph/vertex`, `NoEdges` in `dataflow/graph/graph`, the `RNode` helpers in
|
|
6
|
+
* `r-bridge/lang-4.x/ast/model`, and the collection helpers in `util/collections`.
|
|
6
7
|
*/
|
|
7
8
|
const EDGE = 'dataflow/graph/edge';
|
|
8
9
|
const VERTEX = 'dataflow/graph/vertex';
|
|
9
10
|
const GRAPH = 'dataflow/graph/graph';
|
|
11
|
+
const RTYPE = 'r-bridge/lang-4.x/ast/model/type';
|
|
12
|
+
const CALL_NODE = 'r-bridge/lang-4.x/ast/model/nodes/r-function-call';
|
|
13
|
+
const ARGUMENT_NODE = 'r-bridge/lang-4.x/ast/model/nodes/r-argument';
|
|
14
|
+
const SYMBOL_NODE = 'r-bridge/lang-4.x/ast/model/nodes/r-symbol';
|
|
15
|
+
const LIST_NODE = 'r-bridge/lang-4.x/ast/model/nodes/r-expression-list';
|
|
10
16
|
/** `x.types` under the given path */
|
|
11
17
|
const types = (prefix) => `[${prefix}.type="MemberExpression"][${prefix}.computed=false][${prefix}.property.name="types"]`;
|
|
12
|
-
/** `x
|
|
13
|
-
const
|
|
14
|
-
/** `EdgeType.<Member>` / `VertexType.<Member>` on the right */
|
|
18
|
+
/** `x.<name>` under the given path, without asserting the node kind, which the caller pins down */
|
|
19
|
+
const member = (prefix, name) => `[${prefix}.computed=false][${prefix}.property.name="${name}"]`;
|
|
20
|
+
/** `EdgeType.<Member>` / `VertexType.<Member>` / `RType.<Member>` on the right */
|
|
15
21
|
const rightIs = (name) => `[right.type="MemberExpression"][right.computed=false][right.object.name="${name}"]`;
|
|
16
22
|
/** compared against the literal `0`, either for "some bit is set" or for "none is" */
|
|
17
23
|
const versusZero = (nonZero) => `BinaryExpression[operator=${nonZero ? '/^(!==|>)$/' : '"==="'}][right.type="Literal"][right.value=0]`;
|
|
@@ -19,6 +25,10 @@ const versusZero = (nonZero) => `BinaryExpression[operator=${nonZero ? '/^(!==|>
|
|
|
19
25
|
const mask = '[left.type="BinaryExpression"][left.operator="&"]';
|
|
20
26
|
/** a call of `<prefix>.<property>(...)` */
|
|
21
27
|
const call = (prefix, property) => `[${prefix}.type="CallExpression"][${prefix}.callee.type="MemberExpression"][${prefix}.callee.property.name="${property}"]`;
|
|
28
|
+
/** a call of `<Helper>.<property>(<one argument>)` under the given path */
|
|
29
|
+
const helperCall = (prefix, helper, property) => `${call(prefix, property)}[${prefix}.callee.object.name="${helper}"][${prefix}.arguments.length=1]`;
|
|
30
|
+
/** `x === undefined` / `x !== undefined` under the given path */
|
|
31
|
+
const versusUndefined = (prefix, set) => `[${prefix}.type="BinaryExpression"][${prefix}.operator="${set ? '!==' : '==='}"][${prefix}.right.type="Identifier"][${prefix}.right.name="undefined"]`;
|
|
22
32
|
const destructuringHint = ' Prefer keeping the edge over destructuring `types`, the helpers take it directly and the wrapper object goes away.';
|
|
23
33
|
/** `x.types === T`, on the edge itself or on a destructured `types` */
|
|
24
34
|
const edgeIsOnly = (id, { negated = false, destructured = false } = {}) => ({
|
|
@@ -51,32 +61,65 @@ const edgeStates = (id, helper, what, nonZero) => ({
|
|
|
51
61
|
declaredIn: { 'left.property': EDGE },
|
|
52
62
|
message: `Use \`DfEdge.${helper}\` to ask whether the edge states ${what}.`
|
|
53
63
|
});
|
|
54
|
-
/**
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
/**
|
|
65
|
+
* A discriminator compared against its enum, `v.tag === VertexType.X` and `n.type === RType.X`.
|
|
66
|
+
* `optional` covers the `?.` spelling, which parses as a chain around the same member expression.
|
|
67
|
+
* `declaring` maps a path of the match to the file its symbol has to come from; the enum on the right is
|
|
68
|
+
* always pinned, the property on the left only where one file declares it for every case.
|
|
69
|
+
*/
|
|
70
|
+
const discriminatorIs = (id, { property, enumName, enumFile, propertyFile, helper, hint }, { negated = false, optional = false } = {}) => {
|
|
57
71
|
const left = optional ? 'left.expression' : 'left';
|
|
58
72
|
return {
|
|
59
73
|
id,
|
|
60
|
-
selector: `BinaryExpression[operator="${negated ? '!==' : '==='}"][left.type="${optional ? 'ChainExpression' : 'MemberExpression'}"]${
|
|
61
|
-
capture: {
|
|
62
|
-
replace: `${negated ? '!' : ''}{
|
|
63
|
-
declaredIn: { [`${left}.property`]:
|
|
64
|
-
message:
|
|
74
|
+
selector: `BinaryExpression[operator="${negated ? '!==' : '==='}"][left.type="${optional ? 'ChainExpression' : 'MemberExpression'}"]${member(left, property)}${rightIs(enumName)}`,
|
|
75
|
+
capture: { subject: `${left}.object`, type: 'right' },
|
|
76
|
+
replace: `${negated ? '!' : ''}${helper}.is({{subject}})`,
|
|
77
|
+
declaredIn: { 'right.object': enumFile, ...propertyFile ? { [`${left}.property`]: propertyFile } : {} },
|
|
78
|
+
message: `${hint} (\`${helper}.is\`), it narrows the type as well.`
|
|
65
79
|
};
|
|
66
80
|
};
|
|
67
|
-
/** `
|
|
68
|
-
const
|
|
81
|
+
/** `v.tag === VertexType.X`, with `{{type|last}}` spelling the name of the matching helper */
|
|
82
|
+
const vertexIs = (id, options) => discriminatorIs(id, {
|
|
83
|
+
property: 'tag', enumName: 'VertexType', enumFile: VERTEX, propertyFile: VERTEX,
|
|
84
|
+
helper: '{{type|last}}Vertex', hint: 'Compare through the vertex helper'
|
|
85
|
+
}, options);
|
|
86
|
+
/**
|
|
87
|
+
* `n.type === RType.X`. Every `RType` member has a helper object of the same name prefixed with `R`
|
|
88
|
+
* (`RType.Symbol` to `RSymbol`), so `{{type|last}}` names it without a table.
|
|
89
|
+
* Only the enum is pinned to its file: `type` is declared once per node interface, and on the `RNode`
|
|
90
|
+
* union it resolves to a synthesized symbol, so demanding a declaring file for it would match nothing.
|
|
91
|
+
*/
|
|
92
|
+
const nodeIs = (id, options) => discriminatorIs(id, {
|
|
93
|
+
property: 'type', enumName: 'RType', enumFile: RTYPE,
|
|
94
|
+
helper: 'R{{type|last}}', hint: 'Ask the AST node helper'
|
|
95
|
+
}, options);
|
|
96
|
+
/**
|
|
97
|
+
* `<Helper>.is(x) && <extra check on x>`, the body of a more specific guard written out.
|
|
98
|
+
* `guard` pins down the right-hand side, `subject` is the path of the operand it repeats.
|
|
99
|
+
*/
|
|
100
|
+
const guardWrittenOut = ({ id, helper, member: narrower, file, guard, subject, message, fix }) => ({
|
|
69
101
|
id,
|
|
70
|
-
selector:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
102
|
+
selector: `LogicalExpression[operator="&&"]${helperCall('left', helper, 'is')}${guard}`,
|
|
103
|
+
capture: { node: 'left.arguments.0' },
|
|
104
|
+
replace: `${helper}.${narrower}({{node}})`,
|
|
105
|
+
declaredIn: { 'left.callee.object': file },
|
|
106
|
+
sameText: [['left.arguments.0', subject]],
|
|
107
|
+
...fix === undefined ? {} : { fix },
|
|
108
|
+
message
|
|
109
|
+
});
|
|
110
|
+
/**
|
|
111
|
+
* `symbol.content === 'name'`. `content` of an {@link RSymbol} is an `Identifier`, which carries a namespace
|
|
112
|
+
* and turns into an array once it has one, so `===` against a bare name silently misses `pkg::name`.
|
|
113
|
+
*/
|
|
114
|
+
const symbolName = (id, negated) => ({
|
|
115
|
+
id,
|
|
116
|
+
selector: `BinaryExpression[operator="${negated ? '!==' : '==='}"][left.type="MemberExpression"]${member('left', 'content')}[right.type="Literal"][right.value=type(string)]`,
|
|
117
|
+
capture: { symbol: 'left.object', name: 'right' },
|
|
118
|
+
replace: `Identifier.getName({{symbol}}.content) ${negated ? '!==' : '==='} {{name}}`,
|
|
119
|
+
declaredIn: { 'left.property': SYMBOL_NODE },
|
|
120
|
+
/* which of the two is meant is the author's call, so this is offered rather than applied */
|
|
78
121
|
fix: false,
|
|
79
|
-
message: '
|
|
122
|
+
message: 'An `Identifier` is not its name: `pkg::{{name}}` is an array and never `===` a string. Compare `Identifier.getName({{symbol}}.content)`, or `Identifier.matches` when the namespace should count.'
|
|
80
123
|
});
|
|
81
124
|
const patterns = [
|
|
82
125
|
edgeIsOnly('edge-is-only-type'),
|
|
@@ -95,7 +138,19 @@ const patterns = [
|
|
|
95
138
|
declaredIn: { 'left.property': EDGE },
|
|
96
139
|
message: 'Do not rely on the bitmask being truthy, `DfEdge.includesType` says what is meant.'
|
|
97
140
|
},
|
|
98
|
-
|
|
141
|
+
{
|
|
142
|
+
id: 'vertex-has-origin',
|
|
143
|
+
selector: 'LogicalExpression[operator="&&"]'
|
|
144
|
+
+ `${helperCall('left', 'FunctionCallVertex', 'is')}`
|
|
145
|
+
+ `${call('right', 'includes')}[right.callee.object.type="MemberExpression"][right.callee.object.property.name="origin"]`,
|
|
146
|
+
capture: { vertex: 'left.arguments.0', origin: 'right.arguments.0' },
|
|
147
|
+
replace: 'FunctionCallVertex.hasOrigin({{vertex}}, {{origin}})',
|
|
148
|
+
declaredIn: { 'left.callee.object': VERTEX, 'right.callee.object.property': VERTEX },
|
|
149
|
+
sameText: [['left.arguments.0', 'right.callee.object.object']],
|
|
150
|
+
/* `hasOrigin` is no type predicate, so the replacement can drop a narrowing the code below relies on */
|
|
151
|
+
fix: false,
|
|
152
|
+
message: 'Use `FunctionCallVertex.hasOrigin`, it is exactly this check. Check the narrowing first, `hasOrigin` returns a plain boolean.'
|
|
153
|
+
},
|
|
99
154
|
{
|
|
100
155
|
/* the `[]` fallback allocates on every miss, and these sit in traversal loops */
|
|
101
156
|
id: 'graph-edges-no-alloc',
|
|
@@ -108,6 +163,76 @@ const patterns = [
|
|
|
108
163
|
vertexIs('vertex-is'),
|
|
109
164
|
vertexIs('vertex-is-not', { negated: true }),
|
|
110
165
|
vertexIs('vertex-is-optional', { optional: true }),
|
|
111
|
-
vertexIs('vertex-is-not-optional', { negated: true, optional: true })
|
|
166
|
+
vertexIs('vertex-is-not-optional', { negated: true, optional: true }),
|
|
167
|
+
nodeIs('node-is'),
|
|
168
|
+
nodeIs('node-is-not', { negated: true }),
|
|
169
|
+
nodeIs('node-is-optional', { optional: true }),
|
|
170
|
+
nodeIs('node-is-not-optional', { negated: true, optional: true }),
|
|
171
|
+
guardWrittenOut({
|
|
172
|
+
id: 'call-is-named', helper: 'RFunctionCall', member: 'isNamed', file: CALL_NODE,
|
|
173
|
+
guard: `[right.type="MemberExpression"]${member('right', 'named')}`,
|
|
174
|
+
subject: 'right.object',
|
|
175
|
+
message: 'Use `RFunctionCall.isNamed`, it is this check and narrows to `RNamedFunctionCall`.'
|
|
176
|
+
}),
|
|
177
|
+
guardWrittenOut({
|
|
178
|
+
id: 'call-is-named-strict', helper: 'RFunctionCall', member: 'isNamed', file: CALL_NODE,
|
|
179
|
+
guard: `[right.type="BinaryExpression"][right.operator="==="][right.right.type="Literal"][right.right.value=true]${member('right.left', 'named')}`,
|
|
180
|
+
subject: 'right.left.object',
|
|
181
|
+
message: 'Use `RFunctionCall.isNamed`, it is this check and narrows to `RNamedFunctionCall`.'
|
|
182
|
+
}),
|
|
183
|
+
guardWrittenOut({
|
|
184
|
+
id: 'call-is-unnamed', helper: 'RFunctionCall', member: 'isUnnamed', file: CALL_NODE,
|
|
185
|
+
guard: `[right.type="UnaryExpression"][right.operator="!"][right.argument.type="MemberExpression"]${member('right.argument', 'named')}`,
|
|
186
|
+
subject: 'right.argument.object',
|
|
187
|
+
message: 'Use `RFunctionCall.isUnnamed`, it is this check and narrows to `RUnnamedFunctionCall`.'
|
|
188
|
+
}),
|
|
189
|
+
guardWrittenOut({
|
|
190
|
+
id: 'argument-is-named', helper: 'RArgument', member: 'isNamed', file: ARGUMENT_NODE,
|
|
191
|
+
guard: `${versusUndefined('right', true)}${member('right.left', 'name')}`,
|
|
192
|
+
subject: 'right.left.object',
|
|
193
|
+
message: 'Use `RArgument.isNamed`, it is this check and keeps the `name` non-optional afterwards.'
|
|
194
|
+
}),
|
|
195
|
+
guardWrittenOut({
|
|
196
|
+
id: 'argument-is-with-value', helper: 'RArgument', member: 'isWithValue', file: ARGUMENT_NODE,
|
|
197
|
+
guard: `${versusUndefined('right', true)}${member('right.left', 'value')}`,
|
|
198
|
+
subject: 'right.left.object',
|
|
199
|
+
message: 'Use `RArgument.isWithValue`, it is this check and keeps the `value` non-optional afterwards.'
|
|
200
|
+
}),
|
|
201
|
+
guardWrittenOut({
|
|
202
|
+
id: 'list-is-implicit', helper: 'RExpressionList', member: 'isImplicit', file: LIST_NODE,
|
|
203
|
+
guard: `${versusUndefined('right', false)}${member('right.left', 'grouping')}`,
|
|
204
|
+
subject: 'right.left.object',
|
|
205
|
+
message: 'Use `RExpressionList.isImplicit`, an expression list without `grouping` is exactly the implicit one.'
|
|
206
|
+
}),
|
|
207
|
+
symbolName('symbol-name-comparison', false),
|
|
208
|
+
symbolName('symbol-name-comparison-not', true),
|
|
209
|
+
{
|
|
210
|
+
id: 'array-sum',
|
|
211
|
+
selector: 'CallExpression[callee.type="MemberExpression"][callee.property.name="reduce"][arguments.length=2]'
|
|
212
|
+
+ '[arguments.1.type="Literal"][arguments.1.value=0]'
|
|
213
|
+
+ '[arguments.0.type="ArrowFunctionExpression"][arguments.0.params.length=2]'
|
|
214
|
+
+ '[arguments.0.body.type="BinaryExpression"][arguments.0.body.operator="+"]',
|
|
215
|
+
capture: { array: 'callee.object' },
|
|
216
|
+
replace: 'arraySum({{array}})',
|
|
217
|
+
sameText: [['arguments.0.params.0', 'arguments.0.body.left'], ['arguments.0.params.1', 'arguments.0.body.right']],
|
|
218
|
+
message: 'Use `arraySum`, summing a list is not a place to spell out a fold.'
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
/* `filter` walks the whole list and allocates the matches only to ask whether there is one */
|
|
222
|
+
id: 'some-instead-of-filter-length',
|
|
223
|
+
selector: `${versusZero(true)}[left.type="MemberExpression"]${member('left', 'length')}`
|
|
224
|
+
+ `${call('left.object', 'filter')}[left.object.arguments.length=1]`,
|
|
225
|
+
capture: { array: 'left.object.callee.object', predicate: 'left.object.arguments.0' },
|
|
226
|
+
replace: '{{array}}.some({{predicate}})',
|
|
227
|
+
message: '`filter(...).length > 0` walks the whole list and allocates the matches, `some` stops at the first hit.'
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
id: 'none-instead-of-filter-length',
|
|
231
|
+
selector: `${versusZero(false)}[left.type="MemberExpression"]${member('left', 'length')}`
|
|
232
|
+
+ `${call('left.object', 'filter')}[left.object.arguments.length=1]`,
|
|
233
|
+
capture: { array: 'left.object.callee.object', predicate: 'left.object.arguments.0' },
|
|
234
|
+
replace: '!{{array}}.some({{predicate}})',
|
|
235
|
+
message: '`filter(...).length === 0` walks the whole list and allocates the matches, `!some` stops at the first hit.'
|
|
236
|
+
}
|
|
112
237
|
];
|
|
113
238
|
module.exports = patterns;
|
package/rules/util.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared helpers of the flowR rules.
|
|
3
|
-
* Tags: `@useInstead <target>` names a replacement, `@
|
|
3
|
+
* Tags: `@useInstead <target>` names a replacement, `@lintIgnore [ids]` silences the rules.
|
|
4
4
|
*/
|
|
5
5
|
import type { Rule } from 'eslint';
|
|
6
6
|
import type { Node } from 'estree';
|
|
@@ -8,8 +8,8 @@ import type { TsSymbol, TsTypeChecker, TypeServices } from './ts-types';
|
|
|
8
8
|
/** The type-aware services, or `undefined` if the file is parsed without a program. */
|
|
9
9
|
export declare function typeServices(context: Rule.RuleContext): TypeServices | undefined;
|
|
10
10
|
/**
|
|
11
|
-
* Whether `node` sits below a declaration documented with
|
|
12
|
-
*
|
|
11
|
+
* Whether `node` sits below a declaration documented with `@lintIgnore`, or in a file whose header comment
|
|
12
|
+
* carries it. Guarded by one string search, so untagged files pay nothing.
|
|
13
13
|
*/
|
|
14
14
|
export declare function suppressed(context: Rule.RuleContext, node: Rule.Node, ids: readonly string[]): boolean;
|
|
15
15
|
/** The symbol an ESTree node resolves to, with import aliases followed. */
|
package/rules/util.js
CHANGED
|
@@ -7,7 +7,7 @@ exports.declarationFile = declarationFile;
|
|
|
7
7
|
exports.useInsteadOf = useInsteadOf;
|
|
8
8
|
exports.isApplicable = isApplicable;
|
|
9
9
|
const USE_INSTEAD_TAG = 'useInstead';
|
|
10
|
-
const
|
|
10
|
+
const SUPPRESS_TAG = '@lintIgnore';
|
|
11
11
|
/* ts.SymbolFlags.Alias, spelled out to keep typescript out of the dependencies */
|
|
12
12
|
const ALIAS_FLAG = 1 << 21;
|
|
13
13
|
/** The type-aware services, or `undefined` if the file is parsed without a program. */
|
|
@@ -23,9 +23,6 @@ function silences(comment, ids) {
|
|
|
23
23
|
if (!isDocComment(comment)) {
|
|
24
24
|
return false;
|
|
25
25
|
}
|
|
26
|
-
else if (comment.value.includes('@performanceCritical')) {
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
26
|
const ignore = /@lintIgnore([^\n@*]*)/.exec(comment.value);
|
|
30
27
|
if (!ignore) {
|
|
31
28
|
return false;
|
|
@@ -34,12 +31,12 @@ function silences(comment, ids) {
|
|
|
34
31
|
return listed.length === 0 || listed.some(id => ids.includes(id));
|
|
35
32
|
}
|
|
36
33
|
/**
|
|
37
|
-
* Whether `node` sits below a declaration documented with
|
|
38
|
-
*
|
|
34
|
+
* Whether `node` sits below a declaration documented with `@lintIgnore`, or in a file whose header comment
|
|
35
|
+
* carries it. Guarded by one string search, so untagged files pay nothing.
|
|
39
36
|
*/
|
|
40
37
|
function suppressed(context, node, ids) {
|
|
41
38
|
const sourceCode = context.sourceCode;
|
|
42
|
-
if (!
|
|
39
|
+
if (!sourceCode.text.includes(SUPPRESS_TAG)) {
|
|
43
40
|
return false;
|
|
44
41
|
}
|
|
45
42
|
/* a header comment marks the whole file: above the imports, or separated by a blank line */
|