ai-deslop 0.2.0 → 0.2.2

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.
Files changed (97) hide show
  1. package/README.md +35 -106
  2. package/dist/index.d.ts +18 -0
  3. package/dist/index.js +84 -0
  4. package/dist/rules/no-array-filter-map.d.ts +2 -0
  5. package/dist/rules/no-array-filter-map.js +30 -0
  6. package/dist/rules/no-call-only-assertions.d.ts +2 -0
  7. package/dist/rules/no-call-only-assertions.js +87 -0
  8. package/dist/rules/no-chained-type-assertions.d.ts +2 -0
  9. package/dist/rules/no-chained-type-assertions.js +60 -0
  10. package/dist/rules/no-conditional-empty-object-spread.d.ts +2 -0
  11. package/dist/rules/no-conditional-empty-object-spread.js +40 -0
  12. package/dist/rules/no-duplicate-types.d.ts +2 -0
  13. package/dist/rules/no-duplicate-types.js +143 -0
  14. package/dist/rules/no-known-value-widening.d.ts +2 -0
  15. package/dist/rules/no-known-value-widening.js +300 -0
  16. package/dist/rules/no-module-mocking.d.ts +2 -0
  17. package/dist/rules/no-module-mocking.js +69 -0
  18. package/dist/rules/no-object-parameters.d.ts +2 -0
  19. package/dist/rules/no-object-parameters.js +56 -0
  20. package/dist/rules/no-pass-through-type-alias.d.ts +2 -0
  21. package/dist/rules/no-pass-through-type-alias.js +41 -0
  22. package/dist/rules/no-reduce-accumulator-copy.d.ts +2 -0
  23. package/dist/rules/no-reduce-accumulator-copy.js +99 -0
  24. package/dist/rules/no-reflect-apply.d.ts +2 -0
  25. package/dist/rules/no-reflect-apply.js +25 -0
  26. package/dist/rules/no-reflect-get.d.ts +2 -0
  27. package/dist/rules/no-reflect-get.js +25 -0
  28. package/dist/rules/no-runtime-typeof.d.ts +2 -0
  29. package/dist/rules/no-runtime-typeof.js +64 -0
  30. package/dist/rules/no-shape-in-symbol-names.d.ts +2 -0
  31. package/dist/rules/no-shape-in-symbol-names.js +40 -0
  32. package/dist/rules/no-unknown-parameters.d.ts +2 -0
  33. package/dist/rules/no-unknown-parameters.js +51 -0
  34. package/dist/rules/no-unknown-returns.d.ts +2 -0
  35. package/dist/rules/no-unknown-returns.js +58 -0
  36. package/dist/rules/no-unknown-type-aliases.d.ts +2 -0
  37. package/dist/rules/no-unknown-type-aliases.js +40 -0
  38. package/dist/rules/no-unsafe-dictionary-type.d.ts +2 -0
  39. package/dist/rules/no-unsafe-dictionary-type.js +134 -0
  40. package/dist/rules/no-widen-then-assert.d.ts +2 -0
  41. package/dist/rules/no-widen-then-assert.js +270 -0
  42. package/dist/rules/require-safety-comment-for-type-assertion.d.ts +2 -0
  43. package/dist/rules/require-safety-comment-for-type-assertion.js +100 -0
  44. package/dist/shared/array-method.d.ts +12 -0
  45. package/dist/shared/array-method.js +84 -0
  46. package/dist/shared/dictionary-types.d.ts +20 -0
  47. package/dist/shared/dictionary-types.js +377 -0
  48. package/dist/shared/function-parameters.d.ts +8 -0
  49. package/dist/shared/function-parameters.js +40 -0
  50. package/dist/shared/lexical-type-parameters.d.ts +5 -0
  51. package/dist/shared/lexical-type-parameters.js +47 -0
  52. package/dist/shared/reflect-method.d.ts +3 -0
  53. package/dist/shared/reflect-method.js +20 -0
  54. package/dist/shared/scope.d.ts +3 -0
  55. package/dist/shared/scope.js +11 -0
  56. package/dist/shared/type-alias-resolution.d.ts +23 -0
  57. package/dist/shared/type-alias-resolution.js +162 -0
  58. package/dist/tailwind/candidates.d.ts +14 -0
  59. package/dist/tailwind/candidates.js +24 -0
  60. package/dist/tailwind/design-system.d.ts +3 -0
  61. package/dist/tailwind/design-system.js +41 -0
  62. package/dist/tailwind/rule.d.ts +3 -0
  63. package/dist/tailwind/rule.js +133 -0
  64. package/dist/tailwind/worker.d.ts +1 -0
  65. package/dist/tailwind/worker.js +13 -0
  66. package/package.json +16 -5
  67. package/src/index.ts +0 -87
  68. package/src/rules/no-array-filter-map.ts +0 -28
  69. package/src/rules/no-call-only-assertions.ts +0 -94
  70. package/src/rules/no-chained-type-assertions.ts +0 -77
  71. package/src/rules/no-conditional-empty-object-spread.ts +0 -49
  72. package/src/rules/no-known-value-widening.ts +0 -415
  73. package/src/rules/no-module-mocking.ts +0 -80
  74. package/src/rules/no-object-parameters.ts +0 -83
  75. package/src/rules/no-pass-through-type-alias.ts +0 -52
  76. package/src/rules/no-reduce-accumulator-copy.ts +0 -109
  77. package/src/rules/no-reflect-apply.ts +0 -28
  78. package/src/rules/no-reflect-get.ts +0 -28
  79. package/src/rules/no-runtime-typeof.ts +0 -77
  80. package/src/rules/no-shape-in-symbol-names.ts +0 -46
  81. package/src/rules/no-unknown-parameters.ts +0 -69
  82. package/src/rules/no-unknown-returns.ts +0 -82
  83. package/src/rules/no-unknown-type-aliases.ts +0 -54
  84. package/src/rules/no-unsafe-dictionary-type.ts +0 -154
  85. package/src/rules/no-widen-then-assert.ts +0 -366
  86. package/src/rules/require-safety-comment-for-type-assertion.ts +0 -131
  87. package/src/shared/array-method.ts +0 -94
  88. package/src/shared/dictionary-types.ts +0 -515
  89. package/src/shared/function-parameters.ts +0 -49
  90. package/src/shared/lexical-type-parameters.ts +0 -61
  91. package/src/shared/reflect-method.ts +0 -24
  92. package/src/shared/scope.ts +0 -15
  93. package/src/shared/type-alias-resolution.ts +0 -250
  94. package/src/tailwind/candidates.ts +0 -38
  95. package/src/tailwind/design-system.ts +0 -59
  96. package/src/tailwind/rule.ts +0 -126
  97. package/src/tailwind/worker.ts +0 -38
@@ -0,0 +1,270 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+ const functionBoundaryTypes = new Set([
3
+ "ArrowFunctionExpression",
4
+ "FunctionDeclaration",
5
+ "FunctionExpression",
6
+ "TSDeclareFunction",
7
+ "TSEmptyBodyFunctionExpression",
8
+ ]);
9
+ function unwrapExpressionParentheses(expression) {
10
+ let current = expression;
11
+ while (current.type === "ParenthesizedExpression")
12
+ current = current.expression;
13
+ return current;
14
+ }
15
+ function unwrapTypeParentheses(type) {
16
+ let current = type;
17
+ while (current.type === "TSParenthesizedType")
18
+ current = current.typeAnnotation;
19
+ return current;
20
+ }
21
+ function typeReferenceName(type) {
22
+ return type.typeName.type === "Identifier" ? type.typeName.name : null;
23
+ }
24
+ function isUnknownOrAnyType(type) {
25
+ const unwrapped = unwrapTypeParentheses(type);
26
+ return unwrapped.type === "TSUnknownKeyword" || unwrapped.type === "TSAnyKeyword";
27
+ }
28
+ function isBroadRecordKeyType(type) {
29
+ const unwrapped = unwrapTypeParentheses(type);
30
+ if (unwrapped.type === "TSStringKeyword" ||
31
+ unwrapped.type === "TSNumberKeyword" ||
32
+ unwrapped.type === "TSSymbolKeyword") {
33
+ return true;
34
+ }
35
+ if (unwrapped.type === "TSUnionType")
36
+ return unwrapped.types.every(isBroadRecordKeyType);
37
+ return unwrapped.type === "TSTypeReference" && typeReferenceName(unwrapped) === "PropertyKey";
38
+ }
39
+ function isBroadRecordType(type) {
40
+ const unwrapped = unwrapTypeParentheses(type);
41
+ if (unwrapped.type === "TSTypeReference") {
42
+ if (typeReferenceName(unwrapped) === "Readonly") {
43
+ const [inner] = unwrapped.typeArguments?.params ?? [];
44
+ return inner !== undefined && isBroadRecordType(inner);
45
+ }
46
+ if (typeReferenceName(unwrapped) !== "Record")
47
+ return false;
48
+ const parameters = unwrapped.typeArguments?.params ?? [];
49
+ return (parameters.length === 2 &&
50
+ parameters[0] !== undefined &&
51
+ parameters[1] !== undefined &&
52
+ isBroadRecordKeyType(parameters[0]) &&
53
+ isUnknownOrAnyType(parameters[1]));
54
+ }
55
+ if (unwrapped.type !== "TSTypeLiteral" || unwrapped.members.length !== 1)
56
+ return false;
57
+ const [member] = unwrapped.members;
58
+ const [parameter] = member?.type === "TSIndexSignature" ? member.parameters : [];
59
+ return (member?.type === "TSIndexSignature" &&
60
+ member.parameters.length === 1 &&
61
+ parameter !== undefined &&
62
+ isBroadRecordKeyType(parameter.typeAnnotation.typeAnnotation) &&
63
+ isUnknownOrAnyType(member.typeAnnotation.typeAnnotation));
64
+ }
65
+ function broadTypeKind(type) {
66
+ const unwrapped = unwrapTypeParentheses(type);
67
+ if (unwrapped.type === "TSUnknownKeyword" || unwrapped.type === "TSAnyKeyword")
68
+ return "top";
69
+ if (unwrapped.type === "TSObjectKeyword")
70
+ return "object";
71
+ return isBroadRecordType(unwrapped) ? "record" : null;
72
+ }
73
+ function assertedExpression(node) {
74
+ return unwrapExpressionParentheses(node.expression);
75
+ }
76
+ function assertionFromExpression(expression) {
77
+ const unwrapped = unwrapExpressionParentheses(expression);
78
+ return unwrapped.type === "TSAsExpression" || unwrapped.type === "TSTypeAssertion"
79
+ ? unwrapped
80
+ : null;
81
+ }
82
+ function normalizedTypeText(sourceText, type) {
83
+ return sourceText.slice(type.start, type.end).replaceAll(/\s+/gu, "");
84
+ }
85
+ function typesHaveSameSyntax(sourceText, left, right) {
86
+ return (left !== null &&
87
+ normalizedTypeText(sourceText, unwrapTypeParentheses(left)) ===
88
+ normalizedTypeText(sourceText, unwrapTypeParentheses(right)));
89
+ }
90
+ function isDefinitelyObjectType(type) {
91
+ const unwrapped = unwrapTypeParentheses(type);
92
+ switch (unwrapped.type) {
93
+ case "TSArrayType":
94
+ case "TSConstructorType":
95
+ case "TSFunctionType":
96
+ case "TSMappedType":
97
+ case "TSObjectKeyword":
98
+ case "TSTupleType":
99
+ return true;
100
+ case "TSTypeLiteral":
101
+ return unwrapped.members.length > 0;
102
+ case "TSIntersectionType":
103
+ return unwrapped.types.every(isDefinitelyObjectType);
104
+ case "TSTypeOperator":
105
+ return unwrapped.operator === "readonly" && isDefinitelyObjectType(unwrapped.typeAnnotation);
106
+ default:
107
+ return false;
108
+ }
109
+ }
110
+ function isDefinitelyNarrowerRecordType(type) {
111
+ const unwrapped = unwrapTypeParentheses(type);
112
+ if (unwrapped.type === "TSTypeLiteral") {
113
+ return unwrapped.members.some((member) => member.type !== "TSIndexSignature");
114
+ }
115
+ if (unwrapped.type !== "TSTypeReference")
116
+ return false;
117
+ if (typeReferenceName(unwrapped) === "Readonly") {
118
+ const [inner] = unwrapped.typeArguments?.params ?? [];
119
+ return inner !== undefined && isDefinitelyNarrowerRecordType(inner);
120
+ }
121
+ if (typeReferenceName(unwrapped) !== "Record")
122
+ return false;
123
+ const parameters = unwrapped.typeArguments?.params ?? [];
124
+ return (parameters.length === 2 && parameters[1] !== undefined && !isUnknownOrAnyType(parameters[1]));
125
+ }
126
+ function functionBoundary(node) {
127
+ let current = node.parent;
128
+ while (current !== null && current.type !== "Program") {
129
+ if (functionBoundaryTypes.has(current.type))
130
+ return current;
131
+ current = current.parent;
132
+ }
133
+ return null;
134
+ }
135
+ function resolvedVariableForIdentifier(scopes, identifier) {
136
+ for (const scope of scopes) {
137
+ const reference = scope.references.find((candidate) => candidate.identifier.start === identifier.start &&
138
+ candidate.identifier.end === identifier.end);
139
+ if (reference !== undefined)
140
+ return reference.resolved;
141
+ }
142
+ return null;
143
+ }
144
+ function variableDeclarator(variable) {
145
+ for (const definition of variable.defs) {
146
+ if (definition.type === "Variable" && definition.node.type === "VariableDeclarator") {
147
+ return definition.node;
148
+ }
149
+ }
150
+ return null;
151
+ }
152
+ function knownValueEvidence(expression, scopes, boundary, visitedVariables) {
153
+ const unwrapped = unwrapExpressionParentheses(expression);
154
+ if (unwrapped.type === "TSAsExpression" || unwrapped.type === "TSTypeAssertion") {
155
+ if (broadTypeKind(unwrapped.typeAnnotation) !== null)
156
+ return null;
157
+ return { type: unwrapped.typeAnnotation };
158
+ }
159
+ if (unwrapped.type === "Literal" || unwrapped.type === "TemplateLiteral") {
160
+ return { type: null };
161
+ }
162
+ if (unwrapped.type === "ArrayExpression" ||
163
+ unwrapped.type === "ArrowFunctionExpression" ||
164
+ unwrapped.type === "ClassExpression" ||
165
+ unwrapped.type === "FunctionExpression" ||
166
+ unwrapped.type === "NewExpression" ||
167
+ unwrapped.type === "ObjectExpression") {
168
+ return { type: null };
169
+ }
170
+ if (unwrapped.type !== "Identifier")
171
+ return null;
172
+ const variable = resolvedVariableForIdentifier(scopes, unwrapped);
173
+ if (variable === null || visitedVariables.has(variable))
174
+ return null;
175
+ const annotatedIdentifier = variable.identifiers.find((identifier) => identifier.typeAnnotation !== null && identifier.typeAnnotation !== undefined);
176
+ const annotation = annotatedIdentifier?.typeAnnotation?.typeAnnotation;
177
+ if (annotation !== undefined && annotatedIdentifier !== undefined) {
178
+ if (functionBoundary(annotatedIdentifier) !== boundary || broadTypeKind(annotation) !== null) {
179
+ return null;
180
+ }
181
+ return { type: annotation };
182
+ }
183
+ const declarator = variableDeclarator(variable);
184
+ if (declarator === null ||
185
+ declarator.parent.type !== "VariableDeclaration" ||
186
+ declarator.parent.kind !== "const" ||
187
+ declarator.init === null ||
188
+ variable.references.some((reference) => reference.isWrite() && !reference.init) ||
189
+ functionBoundary(declarator) !== boundary) {
190
+ return null;
191
+ }
192
+ return knownValueEvidence(declarator.init, scopes, boundary, new Set([...visitedVariables, variable]));
193
+ }
194
+ function widenedBinding(variable, scopes) {
195
+ const declarator = variableDeclarator(variable);
196
+ if (declarator === null ||
197
+ declarator.parent.type !== "VariableDeclaration" ||
198
+ declarator.parent.kind !== "const" ||
199
+ declarator.id.type !== "Identifier" ||
200
+ declarator.init === null ||
201
+ variable.references.some((reference) => reference.isWrite() && !reference.init)) {
202
+ return null;
203
+ }
204
+ const boundary = functionBoundary(declarator);
205
+ const declaredType = declarator.id.typeAnnotation?.typeAnnotation;
206
+ const initializerAssertion = assertionFromExpression(declarator.init);
207
+ const initializerBroadKind = initializerAssertion === null ? null : broadTypeKind(initializerAssertion.typeAnnotation);
208
+ const declaredBroadKind = declaredType === undefined ? null : broadTypeKind(declaredType);
209
+ const broadKind = declaredBroadKind ?? initializerBroadKind;
210
+ if (broadKind === null)
211
+ return null;
212
+ const originalExpression = initializerAssertion !== null && initializerBroadKind !== null
213
+ ? assertedExpression(initializerAssertion)
214
+ : declarator.init;
215
+ const evidence = knownValueEvidence(originalExpression, scopes, boundary, new Set([variable]));
216
+ return evidence === null ? null : { broadKind, evidence, declaredAt: declarator.end, boundary };
217
+ }
218
+ function assertionIsNarrower(sourceText, broadKind, evidence, assertedType) {
219
+ if (broadTypeKind(assertedType) !== null)
220
+ return false;
221
+ if (broadKind === "top")
222
+ return true;
223
+ if (typesHaveSameSyntax(sourceText, evidence.type, assertedType))
224
+ return true;
225
+ if (broadKind === "object")
226
+ return isDefinitelyObjectType(assertedType);
227
+ return isDefinitelyNarrowerRecordType(assertedType);
228
+ }
229
+ /** Detect immutable local bindings that erase a known type and are later asserted back to a narrower type. */
230
+ export const noWidenThenAssertRule = defineRule({
231
+ meta: {
232
+ type: "problem",
233
+ docs: {
234
+ description: "Disallow local const flows that explicitly widen a known value before asserting the widened binding to a narrower type.",
235
+ },
236
+ messages: {
237
+ widenThenAssert: 'Binding "{{name}}" discards type evidence and later recreates it with an assertion. Keep the precise type from initialization through use; parse boundary input once.',
238
+ },
239
+ },
240
+ createOnce(context) {
241
+ let scopes = [];
242
+ const checkAssertion = (node) => {
243
+ const expression = assertedExpression(node);
244
+ if (expression.type !== "Identifier")
245
+ return;
246
+ const variable = resolvedVariableForIdentifier(scopes, expression);
247
+ if (variable === null)
248
+ return;
249
+ const widened = widenedBinding(variable, scopes);
250
+ if (widened === null ||
251
+ node.start <= widened.declaredAt ||
252
+ functionBoundary(node) !== widened.boundary ||
253
+ !assertionIsNarrower(context.sourceCode.text, widened.broadKind, widened.evidence, node.typeAnnotation)) {
254
+ return;
255
+ }
256
+ context.report({
257
+ node,
258
+ messageId: "widenThenAssert",
259
+ data: { name: expression.name },
260
+ });
261
+ };
262
+ return {
263
+ Program() {
264
+ scopes = context.sourceCode.scopeManager.scopes;
265
+ },
266
+ TSAsExpression: checkAssertion,
267
+ TSTypeAssertion: checkAssertion,
268
+ };
269
+ },
270
+ });
@@ -0,0 +1,2 @@
1
+ /** Require every non-const type assertion to state the invariant TypeScript cannot express. */
2
+ export declare const requireSafetyCommentForTypeAssertionRule: import("@oxlint/plugins").Rule;
@@ -0,0 +1,100 @@
1
+ import { defineRule } from "@oxlint/plugins";
2
+ const DEFAULT_SAFETY_MARKERS = ["SAFETY"];
3
+ const commentOwnerKinds = new Set([
4
+ "ExpressionStatement",
5
+ "PropertyDefinition",
6
+ "ReturnStatement",
7
+ "ThrowStatement",
8
+ "VariableDeclaration",
9
+ ]);
10
+ function isConstAssertion(node) {
11
+ return (node.typeAnnotation.type === "TSTypeReference" &&
12
+ node.typeAnnotation.typeName.type === "Identifier" &&
13
+ node.typeAnnotation.typeName.name === "const");
14
+ }
15
+ function configuredSafetyMarkers(option) {
16
+ if (typeof option !== "object" || option === null || !("markers" in option)) {
17
+ return DEFAULT_SAFETY_MARKERS;
18
+ }
19
+ const configured = option.markers;
20
+ if (!Array.isArray(configured))
21
+ return DEFAULT_SAFETY_MARKERS;
22
+ const markers = configured.flatMap((marker) => typeof marker === "string" && marker.trim().length > 0 ? [marker.trim()] : []);
23
+ return markers.length > 0 ? markers : DEFAULT_SAFETY_MARKERS;
24
+ }
25
+ function markerPattern(markers) {
26
+ const alternation = markers
27
+ .map((marker) => marker.replaceAll(/[.*+?^${}()|[\]\\]/gu, String.raw `\$&`))
28
+ .join("|");
29
+ return new RegExp(String.raw `(?:^|[^\p{L}\p{N}_])(?:${alternation})\s*:\s*\S`, "u");
30
+ }
31
+ function hasSafetyJustificationBefore(sourceCode, owner, assertion, pattern) {
32
+ return sourceCode
33
+ .getCommentsBefore(owner)
34
+ .some((comment) => comment.end <= assertion.start && pattern.test(comment.value));
35
+ }
36
+ function hasSafetyComment(sourceCode, node, pattern) {
37
+ let current = node;
38
+ while (true) {
39
+ if (hasSafetyJustificationBefore(sourceCode, current, node, pattern))
40
+ return true;
41
+ if (commentOwnerKinds.has(current.type)) {
42
+ const exportDeclaration = current.parent;
43
+ return (exportDeclaration.type === "ExportNamedDeclaration" &&
44
+ exportDeclaration.declaration === current &&
45
+ hasSafetyJustificationBefore(sourceCode, exportDeclaration, node, pattern));
46
+ }
47
+ if (current.parent.type === "Program")
48
+ return false;
49
+ current = current.parent;
50
+ }
51
+ }
52
+ /** Require every non-const type assertion to state the invariant TypeScript cannot express. */
53
+ export const requireSafetyCommentForTypeAssertionRule = defineRule({
54
+ meta: {
55
+ type: "problem",
56
+ docs: {
57
+ description: "Require a nearby SAFETY comment for every TypeScript type assertion except const assertions.",
58
+ },
59
+ messages: {
60
+ missingSafetyComment: "This type assertion has no `{{marker}}:` justification. State the checked invariant immediately before the assertion or its containing statement.",
61
+ },
62
+ schema: [
63
+ {
64
+ type: "object",
65
+ properties: {
66
+ markers: {
67
+ type: "array",
68
+ items: { type: "string", minLength: 1 },
69
+ minItems: 1,
70
+ uniqueItems: true,
71
+ },
72
+ },
73
+ additionalProperties: false,
74
+ },
75
+ ],
76
+ defaultOptions: [{ markers: ["SAFETY"] }],
77
+ },
78
+ createOnce(context) {
79
+ const patterns = new Map();
80
+ const checkAssertion = (node) => {
81
+ if (isConstAssertion(node))
82
+ return;
83
+ const markers = configuredSafetyMarkers(context.options?.[0]);
84
+ const patternKey = markers.join("\u0000");
85
+ const pattern = patterns.get(patternKey) ?? markerPattern(markers);
86
+ patterns.set(patternKey, pattern);
87
+ if (hasSafetyComment(context.sourceCode, node, pattern))
88
+ return;
89
+ context.report({
90
+ node,
91
+ messageId: "missingSafetyComment",
92
+ data: { marker: markers[0] ?? DEFAULT_SAFETY_MARKERS[0] },
93
+ });
94
+ };
95
+ return {
96
+ TSAsExpression: checkAssertion,
97
+ TSTypeAssertion: checkAssertion,
98
+ };
99
+ },
100
+ });
@@ -0,0 +1,12 @@
1
+ import type { ESTree, SourceCode, Variable } from "@oxlint/plugins";
2
+ /** Unwrap syntax-only wrappers when inspecting array methods and accumulator references. */
3
+ export declare function unwrapArrayExpression(node: ESTree.Node): ESTree.Node;
4
+ /** Resolve a local binding by scope, not by identifier spelling. */
5
+ export declare function resolveArrayBinding(sourceCode: SourceCode, node: ESTree.Node): Variable | null;
6
+ /** Read static method names, including computed string literals, without evaluating expressions. */
7
+ export declare function arrayMethodTarget(node: ESTree.Node): {
8
+ readonly name: string;
9
+ readonly object: ESTree.Node;
10
+ } | null;
11
+ /** Recognize local array evidence; unknown receivers and iterator pipelines are deliberately excluded. */
12
+ export declare function isKnownArrayExpression(sourceCode: SourceCode, node: ESTree.Node, visited?: Set<Variable>): boolean;
@@ -0,0 +1,84 @@
1
+ /** Unwrap syntax-only wrappers when inspecting array methods and accumulator references. */
2
+ export function unwrapArrayExpression(node) {
3
+ while (node.type === "ParenthesizedExpression" ||
4
+ node.type === "ChainExpression" ||
5
+ node.type === "TSAsExpression" ||
6
+ node.type === "TSTypeAssertion" ||
7
+ node.type === "TSNonNullExpression" ||
8
+ node.type === "TSSatisfiesExpression") {
9
+ node = node.expression;
10
+ }
11
+ return node;
12
+ }
13
+ /** Resolve a local binding by scope, not by identifier spelling. */
14
+ export function resolveArrayBinding(sourceCode, node) {
15
+ node = unwrapArrayExpression(node);
16
+ if (node.type !== "Identifier")
17
+ return null;
18
+ let scope = sourceCode.getScope(node);
19
+ while (scope !== null) {
20
+ const variable = scope.set.get(node.name);
21
+ if (variable !== undefined)
22
+ return variable;
23
+ scope = scope.upper;
24
+ }
25
+ return null;
26
+ }
27
+ /** Read static method names, including computed string literals, without evaluating expressions. */
28
+ export function arrayMethodTarget(node) {
29
+ node = unwrapArrayExpression(node);
30
+ if (node.type !== "MemberExpression")
31
+ return null;
32
+ const property = node.property;
33
+ if (!node.computed && property.type === "Identifier") {
34
+ return { name: property.name, object: node.object };
35
+ }
36
+ if (node.computed && property.type === "Literal" && typeof property.value === "string") {
37
+ return { name: property.value, object: node.object };
38
+ }
39
+ return null;
40
+ }
41
+ function isArrayAnnotation(type) {
42
+ if (type.type === "TSArrayType" || type.type === "TSTupleType")
43
+ return true;
44
+ if (type.type === "TSParenthesizedType")
45
+ return isArrayAnnotation(type.typeAnnotation);
46
+ if (type.type === "TSTypeOperator" && type.operator === "readonly") {
47
+ return isArrayAnnotation(type.typeAnnotation);
48
+ }
49
+ return (type.type === "TSTypeReference" && type.typeName.type === "Identifier" &&
50
+ (type.typeName.name === "Array" || type.typeName.name === "ReadonlyArray"));
51
+ }
52
+ /** Recognize local array evidence; unknown receivers and iterator pipelines are deliberately excluded. */
53
+ export function isKnownArrayExpression(sourceCode, node, visited = new Set()) {
54
+ node = unwrapArrayExpression(node);
55
+ if (node.type === "ArrayExpression")
56
+ return true;
57
+ if (node.type === "CallExpression") {
58
+ const method = arrayMethodTarget(node.callee);
59
+ return (method !== null &&
60
+ ["map", "filter", "flatMap", "slice", "concat", "toSorted", "toReversed", "toSpliced"].includes(method.name) &&
61
+ isKnownArrayExpression(sourceCode, method.object, visited));
62
+ }
63
+ if (node.type !== "Identifier")
64
+ return false;
65
+ const variable = resolveArrayBinding(sourceCode, node);
66
+ if (variable === null || visited.has(variable))
67
+ return false;
68
+ visited.add(variable);
69
+ if (variable.references.some(reference => reference.isWrite() && !reference.init))
70
+ return false;
71
+ for (const identifier of variable.identifiers) {
72
+ const annotation = identifier.typeAnnotation?.typeAnnotation;
73
+ if (annotation !== undefined)
74
+ return isArrayAnnotation(annotation);
75
+ }
76
+ for (const definition of variable.defs) {
77
+ if (definition.type === "Variable" && definition.node.type === "VariableDeclarator" &&
78
+ definition.node.id.type === "Identifier" && definition.node.init !== null &&
79
+ definition.node.parent.type === "VariableDeclaration" && definition.node.parent.kind === "const") {
80
+ return isKnownArrayExpression(sourceCode, definition.node.init, visited);
81
+ }
82
+ }
83
+ return false;
84
+ }
@@ -0,0 +1,20 @@
1
+ import type { ESTree } from "@oxlint/plugins";
2
+ import { type TypeAliasEnvironment as LexicalTypeAliasEnvironment } from "./type-alias-resolution.ts";
3
+ export type UnsafeDictionary = {
4
+ readonly kind: "unsafe-dictionary";
5
+ readonly unsafeValue: "any" | "empty-object" | "object" | "union" | "unknown";
6
+ };
7
+ export type WideningTargetKind = "anonymous object" | "generic container" | "object" | "open dictionary" | "unknown";
8
+ export type WideningTarget = {
9
+ readonly kind: WideningTargetKind;
10
+ };
11
+ export type TypeEnvironment = {
12
+ readonly interfaces: ReadonlyMap<string, readonly ESTree.TSInterfaceDeclaration[]>;
13
+ readonly typeAliases: LexicalTypeAliasEnvironment;
14
+ };
15
+ export declare function createTypeEnvironment(program: ESTree.Program, visitorKeys: Readonly<Record<string, readonly string[]>>): TypeEnvironment;
16
+ export declare function classifyUnsafeDictionaryValue(valueType: ESTree.TSType, environment: TypeEnvironment): UnsafeDictionary | null;
17
+ export declare function classifyUnsafeDictionary(type: ESTree.TSType, environment: TypeEnvironment): UnsafeDictionary | null;
18
+ export declare function classifyWideningTarget(type: ESTree.TSType, environment: TypeEnvironment): WideningTarget | null;
19
+ export declare function isPopulatedObjectExpression(expression: ESTree.Expression): boolean;
20
+ export declare function isKnownEvidenceExpression(expression: ESTree.Expression): boolean;