@flint.fyi/ts 0.19.1 → 0.20.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/lib/plugin.d.ts +30 -0
- package/lib/plugin.js +4 -0
- package/lib/rules/asyncFunctionAwaits.d.ts +10 -0
- package/lib/rules/asyncFunctionAwaits.js +55 -0
- package/lib/rules/classAssignments.js +4 -7
- package/lib/rules/constantAssignments.js +4 -7
- package/lib/rules/exceptionAssignments.js +4 -7
- package/lib/rules/functionAssignments.js +4 -7
- package/lib/rules/nullishCoalescingOperators.js +22 -10
- package/lib/rules/parameterReassignments.js +18 -49
- package/lib/rules/restrictedImports.d.ts +27 -0
- package/lib/rules/restrictedImports.js +166 -0
- package/lib/rules/typeImports.js +1 -2
- package/lib/rules/unassignedVariables.js +2 -5
- package/lib/rules/utils/collectAccessorPairs.d.ts +1 -1
- package/lib/rules/utils/discriminateAnyType.d.ts +1 -1
- package/lib/rules/utils/equalityOperators.d.ts +1 -1
- package/lib/rules/utils/formatReportedType.d.ts +1 -1
- package/lib/rules/utils/getRegExpConstruction.d.ts +1 -1
- package/lib/rules/utils/isDirectEqualityCheck.d.ts +1 -1
- package/lib/rules/utils/isUnsafeAssignment.d.ts +1 -1
- package/lib/type-utils/getSpecifierNames.d.ts +3 -0
- package/lib/type-utils/getSpecifierNames.js +9 -0
- package/lib/type-utils/isDeclaredInModuleBlock.d.ts +3 -0
- package/lib/type-utils/isDeclaredInModuleBlock.js +14 -0
- package/lib/type-utils/isFromFile.d.ts +3 -0
- package/lib/type-utils/isFromFile.js +13 -0
- package/lib/type-utils/isFromPackage.d.ts +3 -0
- package/lib/type-utils/isFromPackage.js +16 -0
- package/lib/type-utils/matchesSpecifier.d.ts +4 -0
- package/lib/type-utils/matchesSpecifier.js +20 -0
- package/lib/type-utils/schemas.d.ts +15 -0
- package/lib/type-utils/schemas.js +25 -0
- package/package.json +6 -6
- package/lib/rules/utils/forEachChild.d.ts +0 -4
- package/lib/rules/utils/forEachChild.js +0 -7
package/lib/plugin.d.ts
CHANGED
|
@@ -255,6 +255,13 @@ declare const ts: _flint_fyi_core0.Plugin<_flint_fyi_core0.RuleAbout<string>, "j
|
|
|
255
255
|
readonly pluginId: string;
|
|
256
256
|
readonly url: string;
|
|
257
257
|
}, "preferShorthand", undefined>, _flint_fyi_core0.Rule<{
|
|
258
|
+
readonly description: "Reports async functions that do not use await.";
|
|
259
|
+
readonly id: "asyncFunctionAwaits";
|
|
260
|
+
readonly presets: readonly ["logicalStrict"];
|
|
261
|
+
} & {
|
|
262
|
+
readonly pluginId: string;
|
|
263
|
+
readonly url: string;
|
|
264
|
+
}, "missingAwait", undefined>, _flint_fyi_core0.Rule<{
|
|
258
265
|
readonly description: "Reports using async functions as Promise executor functions.";
|
|
259
266
|
readonly id: "asyncPromiseExecutors";
|
|
260
267
|
readonly presets: readonly ["logical", "logicalStrict"];
|
|
@@ -1816,6 +1823,29 @@ declare const ts: _flint_fyi_core0.Plugin<_flint_fyi_core0.RuleAbout<string>, "j
|
|
|
1816
1823
|
readonly url: string;
|
|
1817
1824
|
}, "restricted", {
|
|
1818
1825
|
deny: zod_v40.ZodOptional<zod_v40.ZodArray<zod_v40.ZodString>>;
|
|
1826
|
+
}>, _flint_fyi_core0.Rule<{
|
|
1827
|
+
readonly description: "Restricts specified modules from being imported.";
|
|
1828
|
+
readonly id: "restrictedImports";
|
|
1829
|
+
} & {
|
|
1830
|
+
readonly pluginId: string;
|
|
1831
|
+
readonly url: string;
|
|
1832
|
+
}, "restricted" | "moduleRestricted" | "moduleRestrictedWithMessage" | "namespaceRestricted" | "namespaceRestrictedWithMessage" | "restrictedWithMessage", {
|
|
1833
|
+
restrictions: zod_v40.ZodDefault<zod_v40.ZodArray<zod_v40.ZodObject<{
|
|
1834
|
+
allowTypeImports: zod_v40.ZodOptional<zod_v40.ZodBoolean>;
|
|
1835
|
+
message: zod_v40.ZodOptional<zod_v40.ZodString>;
|
|
1836
|
+
specifier: zod_v40.ZodUnion<readonly [zod_v40.ZodObject<{
|
|
1837
|
+
from: zod_v40.ZodLiteral<"file">;
|
|
1838
|
+
name: zod_v40.ZodOptional<zod_v40.ZodUnion<readonly [zod_v40.ZodString, zod_v40.ZodArray<zod_v40.ZodString>]>>;
|
|
1839
|
+
path: zod_v40.ZodOptional<zod_v40.ZodString>;
|
|
1840
|
+
}, zod_v4_core0.$strip>, zod_v40.ZodObject<{
|
|
1841
|
+
from: zod_v40.ZodLiteral<"lib">;
|
|
1842
|
+
name: zod_v40.ZodOptional<zod_v40.ZodUnion<readonly [zod_v40.ZodString, zod_v40.ZodArray<zod_v40.ZodString>]>>;
|
|
1843
|
+
}, zod_v4_core0.$strip>, zod_v40.ZodObject<{
|
|
1844
|
+
from: zod_v40.ZodLiteral<"package">;
|
|
1845
|
+
name: zod_v40.ZodOptional<zod_v40.ZodUnion<readonly [zod_v40.ZodString, zod_v40.ZodArray<zod_v40.ZodString>]>>;
|
|
1846
|
+
package: zod_v40.ZodString;
|
|
1847
|
+
}, zod_v4_core0.$strip>]>;
|
|
1848
|
+
}, zod_v4_core0.$strip>>>;
|
|
1819
1849
|
}>, _flint_fyi_core0.Rule<{
|
|
1820
1850
|
readonly description: "Reports using assignment expressions in return statements.";
|
|
1821
1851
|
readonly id: "returnAssignments";
|
package/lib/plugin.js
CHANGED
|
@@ -33,6 +33,7 @@ import arrayTypes_default from "./rules/arrayTypes.js";
|
|
|
33
33
|
import arrayUnnecessaryLengthChecks_default from "./rules/arrayUnnecessaryLengthChecks.js";
|
|
34
34
|
import asConstAssertions_default from "./rules/asConstAssertions.js";
|
|
35
35
|
import assignmentOperatorShorthands_default from "./rules/assignmentOperatorShorthands.js";
|
|
36
|
+
import asyncFunctionAwaits_default from "./rules/asyncFunctionAwaits.js";
|
|
36
37
|
import asyncPromiseExecutors_default from "./rules/asyncPromiseExecutors.js";
|
|
37
38
|
import asyncUnnecessaryPromiseWrappers_default from "./rules/asyncUnnecessaryPromiseWrappers.js";
|
|
38
39
|
import atAccesses_default from "./rules/atAccesses.js";
|
|
@@ -248,6 +249,7 @@ import regexZeroQuantifiers_default from "./rules/regexZeroQuantifiers.js";
|
|
|
248
249
|
import requireImports_default from "./rules/requireImports.js";
|
|
249
250
|
import responseJsonMethods_default from "./rules/responseJsonMethods.js";
|
|
250
251
|
import restrictedIdentifiers_default from "./rules/restrictedIdentifiers.js";
|
|
252
|
+
import restrictedImports_default from "./rules/restrictedImports.js";
|
|
251
253
|
import returnAssignments_default from "./rules/returnAssignments.js";
|
|
252
254
|
import returnThisTypes_default from "./rules/returnThisTypes.js";
|
|
253
255
|
import selfAssignments_default from "./rules/selfAssignments.js";
|
|
@@ -344,6 +346,7 @@ const ts = createPlugin({
|
|
|
344
346
|
arrayUnnecessaryLengthChecks_default,
|
|
345
347
|
asConstAssertions_default,
|
|
346
348
|
assignmentOperatorShorthands_default,
|
|
349
|
+
asyncFunctionAwaits_default,
|
|
347
350
|
asyncPromiseExecutors_default,
|
|
348
351
|
asyncUnnecessaryPromiseWrappers_default,
|
|
349
352
|
atAccesses_default,
|
|
@@ -560,6 +563,7 @@ const ts = createPlugin({
|
|
|
560
563
|
requireImports_default,
|
|
561
564
|
responseJsonMethods_default,
|
|
562
565
|
restrictedIdentifiers_default,
|
|
566
|
+
restrictedImports_default,
|
|
563
567
|
returnAssignments_default,
|
|
564
568
|
returnThisTypes_default,
|
|
565
569
|
selfAssignments_default,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare const _default: import("@flint.fyi/core").Rule<{
|
|
2
|
+
readonly description: "Reports async functions that do not use await.";
|
|
3
|
+
readonly id: "asyncFunctionAwaits";
|
|
4
|
+
readonly presets: readonly ["logicalStrict"];
|
|
5
|
+
} & {
|
|
6
|
+
readonly pluginId: string;
|
|
7
|
+
readonly url: string;
|
|
8
|
+
}, "missingAwait", undefined>;
|
|
9
|
+
export default _default;
|
|
10
|
+
//# sourceMappingURL=asyncFunctionAwaits.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ruleCreator } from "./ruleCreator.js";
|
|
2
|
+
import { getTSNodeRange, typescriptLanguage } from "@flint.fyi/typescript-language";
|
|
3
|
+
import * as ts$1 from "typescript";
|
|
4
|
+
import * as tsutils from "ts-api-utils";
|
|
5
|
+
//#region src/rules/asyncFunctionAwaits.ts
|
|
6
|
+
var asyncFunctionAwaits_default = ruleCreator.createRule(typescriptLanguage, {
|
|
7
|
+
about: {
|
|
8
|
+
description: "Reports async functions that do not use await.",
|
|
9
|
+
id: "asyncFunctionAwaits",
|
|
10
|
+
presets: ["logicalStrict"]
|
|
11
|
+
},
|
|
12
|
+
messages: { missingAwait: {
|
|
13
|
+
primary: "This function is marked `async` but does not contain an `await` expression or return a Promise.",
|
|
14
|
+
secondary: ["Async functions always wrap their return value in a Promise, which adds overhead if you're not using await.", "This may indicate incomplete implementation or leftover code after refactoring."],
|
|
15
|
+
suggestions: ["Add an `await` expression if you need to wait for asynchronous operations.", "Remove the `async` keyword if the function doesn't need to be asynchronous."]
|
|
16
|
+
} },
|
|
17
|
+
setup(context) {
|
|
18
|
+
function checkFunction(node, { sourceFile, typeChecker }) {
|
|
19
|
+
const asyncModifier = node.modifiers?.find((modifier) => modifier.kind === ts$1.SyntaxKind.AsyncKeyword);
|
|
20
|
+
if (!asyncModifier || node.asteriskToken || !node.body || isEmptyBody(node.body) || checkForAwait(node.body) || bodyReturnsThenable(node.body, typeChecker)) return;
|
|
21
|
+
context.report({
|
|
22
|
+
message: "missingAwait",
|
|
23
|
+
range: getTSNodeRange(asyncModifier, sourceFile)
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return { visitors: {
|
|
27
|
+
ArrowFunction: checkFunction,
|
|
28
|
+
FunctionDeclaration: checkFunction,
|
|
29
|
+
FunctionExpression: checkFunction,
|
|
30
|
+
MethodDeclaration: checkFunction
|
|
31
|
+
} };
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
function bodyReturnsThenable(body, typeChecker) {
|
|
35
|
+
if (!ts$1.isBlock(body)) return tsutils.isThenableType(typeChecker, body);
|
|
36
|
+
function checkReturnStatements(node) {
|
|
37
|
+
if (ts$1.isReturnStatement(node) && node.expression && tsutils.isThenableType(typeChecker, node.expression)) return true;
|
|
38
|
+
if (tsutils.isFunctionScopeBoundary(node)) return false;
|
|
39
|
+
return ts$1.forEachChild(node, checkReturnStatements);
|
|
40
|
+
}
|
|
41
|
+
return ts$1.forEachChild(body, checkReturnStatements);
|
|
42
|
+
}
|
|
43
|
+
function checkForAwait(node) {
|
|
44
|
+
if (ts$1.isAwaitExpression(node)) return true;
|
|
45
|
+
if (ts$1.isForOfStatement(node) && node.awaitModifier) return true;
|
|
46
|
+
if (tsutils.isFunctionScopeBoundary(node)) return false;
|
|
47
|
+
return ts$1.forEachChild(node, checkForAwait);
|
|
48
|
+
}
|
|
49
|
+
function isEmptyBody(body) {
|
|
50
|
+
return ts$1.isBlock(body) && !body.statements.length;
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { asyncFunctionAwaits_default as default };
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=asyncFunctionAwaits.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ruleCreator } from "./ruleCreator.js";
|
|
2
|
-
import { getModifyingReferences, typescriptLanguage } from "@flint.fyi/typescript-language";
|
|
2
|
+
import { getModifyingReferences, getTSNodeRange, typescriptLanguage } from "@flint.fyi/typescript-language";
|
|
3
3
|
//#region src/rules/classAssignments.ts
|
|
4
4
|
var classAssignments_default = ruleCreator.createRule(typescriptLanguage, {
|
|
5
5
|
about: {
|
|
@@ -13,15 +13,12 @@ var classAssignments_default = ruleCreator.createRule(typescriptLanguage, {
|
|
|
13
13
|
suggestions: ["Use a different variable name if you need to store a modified reference to a class."]
|
|
14
14
|
} },
|
|
15
15
|
setup(context) {
|
|
16
|
-
return { visitors: { ClassDeclaration: (node, { sourceFile
|
|
16
|
+
return { visitors: { ClassDeclaration: (node, { sourceFile }) => {
|
|
17
17
|
if (!node.name) return;
|
|
18
|
-
const modifyingReferences = getModifyingReferences(node.name, sourceFile
|
|
18
|
+
const modifyingReferences = getModifyingReferences(node.name, sourceFile);
|
|
19
19
|
for (const reference of modifyingReferences) context.report({
|
|
20
20
|
message: "noClassAssign",
|
|
21
|
-
range:
|
|
22
|
-
begin: reference.getStart(sourceFile),
|
|
23
|
-
end: reference.getEnd()
|
|
24
|
-
}
|
|
21
|
+
range: getTSNodeRange(reference, sourceFile)
|
|
25
22
|
});
|
|
26
23
|
} } };
|
|
27
24
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ruleCreator } from "./ruleCreator.js";
|
|
2
|
-
import { getModifyingReferences, typescriptLanguage } from "@flint.fyi/typescript-language";
|
|
2
|
+
import { getModifyingReferences, getTSNodeRange, typescriptLanguage } from "@flint.fyi/typescript-language";
|
|
3
3
|
import ts, { SyntaxKind } from "typescript";
|
|
4
4
|
//#region src/rules/constantAssignments.ts
|
|
5
5
|
var constantAssignments_default = ruleCreator.createRule(typescriptLanguage, {
|
|
@@ -20,18 +20,15 @@ var constantAssignments_default = ruleCreator.createRule(typescriptLanguage, {
|
|
|
20
20
|
for (const element of name.elements) if (element.kind === SyntaxKind.BindingElement) identifiers.push(...collectBindingElements(element.name));
|
|
21
21
|
return identifiers;
|
|
22
22
|
}
|
|
23
|
-
return { visitors: { VariableDeclarationList: (node, { sourceFile
|
|
23
|
+
return { visitors: { VariableDeclarationList: (node, { sourceFile }) => {
|
|
24
24
|
if (!(node.flags & ts.NodeFlags.Const) || !node.declarations.length) return;
|
|
25
25
|
for (const declaration of node.declarations) {
|
|
26
26
|
const identifiers = collectBindingElements(declaration.name);
|
|
27
27
|
for (const identifier of identifiers) {
|
|
28
|
-
const modifyingReferences = getModifyingReferences(identifier, sourceFile
|
|
28
|
+
const modifyingReferences = getModifyingReferences(identifier, sourceFile);
|
|
29
29
|
for (const reference of modifyingReferences) context.report({
|
|
30
30
|
message: "noConstAssign",
|
|
31
|
-
range:
|
|
32
|
-
begin: reference.getStart(sourceFile),
|
|
33
|
-
end: reference.getEnd()
|
|
34
|
-
}
|
|
31
|
+
range: getTSNodeRange(reference, sourceFile)
|
|
35
32
|
});
|
|
36
33
|
}
|
|
37
34
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ruleCreator } from "./ruleCreator.js";
|
|
2
|
-
import { getModifyingReferences, typescriptLanguage } from "@flint.fyi/typescript-language";
|
|
2
|
+
import { getModifyingReferences, getTSNodeRange, typescriptLanguage } from "@flint.fyi/typescript-language";
|
|
3
3
|
import { SyntaxKind } from "typescript";
|
|
4
4
|
//#region src/rules/exceptionAssignments.ts
|
|
5
5
|
var exceptionAssignments_default = ruleCreator.createRule(typescriptLanguage, {
|
|
@@ -20,17 +20,14 @@ var exceptionAssignments_default = ruleCreator.createRule(typescriptLanguage, {
|
|
|
20
20
|
else for (const element of name.elements) if (element.kind === SyntaxKind.BindingElement) identifiers.push(...collectBindingElements(element.name));
|
|
21
21
|
return identifiers;
|
|
22
22
|
}
|
|
23
|
-
return { visitors: { CatchClause: (node, { sourceFile
|
|
23
|
+
return { visitors: { CatchClause: (node, { sourceFile }) => {
|
|
24
24
|
if (!node.variableDeclaration?.name) return;
|
|
25
25
|
const identifiers = collectBindingElements(node.variableDeclaration.name);
|
|
26
26
|
for (const identifier of identifiers) {
|
|
27
|
-
const modifyingReferences = getModifyingReferences(identifier, sourceFile
|
|
27
|
+
const modifyingReferences = getModifyingReferences(identifier, sourceFile);
|
|
28
28
|
for (const reference of modifyingReferences) context.report({
|
|
29
29
|
message: "noExAssign",
|
|
30
|
-
range:
|
|
31
|
-
begin: reference.getStart(sourceFile),
|
|
32
|
-
end: reference.getEnd()
|
|
33
|
-
}
|
|
30
|
+
range: getTSNodeRange(reference, sourceFile)
|
|
34
31
|
});
|
|
35
32
|
}
|
|
36
33
|
} } };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ruleCreator } from "./ruleCreator.js";
|
|
2
|
-
import { getModifyingReferences, typescriptLanguage } from "@flint.fyi/typescript-language";
|
|
2
|
+
import { getModifyingReferences, getTSNodeRange, typescriptLanguage } from "@flint.fyi/typescript-language";
|
|
3
3
|
//#region src/rules/functionAssignments.ts
|
|
4
4
|
var functionAssignments_default = ruleCreator.createRule(typescriptLanguage, {
|
|
5
5
|
about: {
|
|
@@ -13,15 +13,12 @@ var functionAssignments_default = ruleCreator.createRule(typescriptLanguage, {
|
|
|
13
13
|
suggestions: ["Use a function expression or const variable instead if you need to reassign the function."]
|
|
14
14
|
} },
|
|
15
15
|
setup(context) {
|
|
16
|
-
return { visitors: { FunctionDeclaration: (node, { sourceFile
|
|
16
|
+
return { visitors: { FunctionDeclaration: (node, { sourceFile }) => {
|
|
17
17
|
if (!node.name) return;
|
|
18
|
-
const modifyingReferences = getModifyingReferences(node.name, sourceFile
|
|
18
|
+
const modifyingReferences = getModifyingReferences(node.name, sourceFile);
|
|
19
19
|
for (const reference of modifyingReferences) context.report({
|
|
20
20
|
message: "noFunctionAssignment",
|
|
21
|
-
range:
|
|
22
|
-
begin: reference.getStart(sourceFile),
|
|
23
|
-
end: reference.getEnd()
|
|
24
|
-
}
|
|
21
|
+
range: getTSNodeRange(reference, sourceFile)
|
|
25
22
|
});
|
|
26
23
|
} } };
|
|
27
24
|
}
|
|
@@ -111,6 +111,17 @@ function getComparisonOperator(node) {
|
|
|
111
111
|
default: return "";
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
|
+
function getIfStatementNullishCheckValue(node) {
|
|
115
|
+
switch (node.expression.kind) {
|
|
116
|
+
case ts.SyntaxKind.BinaryExpression:
|
|
117
|
+
if (isNullLikeComparison(node.expression)) return extractValueFromComparison(node.expression).value ?? void 0;
|
|
118
|
+
return;
|
|
119
|
+
case ts.SyntaxKind.PrefixUnaryExpression:
|
|
120
|
+
if (node.expression.operator === ts.SyntaxKind.ExclamationToken) return node.expression.operand;
|
|
121
|
+
return;
|
|
122
|
+
default: return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
114
125
|
function getTypeFlags(type) {
|
|
115
126
|
let flags = 0;
|
|
116
127
|
for (const constituent of tsutils.unionConstituents(type)) for (const subConstituent of tsutils.intersectionConstituents(constituent)) flags |= subConstituent.getFlags();
|
|
@@ -143,13 +154,6 @@ function isFalsyLiteralType(part) {
|
|
|
143
154
|
}
|
|
144
155
|
return false;
|
|
145
156
|
}
|
|
146
|
-
function isIfStatementNullishCheck(node) {
|
|
147
|
-
switch (node.expression.kind) {
|
|
148
|
-
case ts.SyntaxKind.BinaryExpression: return isNullLikeComparison(node.expression);
|
|
149
|
-
case ts.SyntaxKind.PrefixUnaryExpression: return node.expression.operator === ts.SyntaxKind.ExclamationToken;
|
|
150
|
-
default: return false;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
157
|
function isMixedLogicalExpression(node) {
|
|
154
158
|
const seen = /* @__PURE__ */ new Set();
|
|
155
159
|
const queue = [
|
|
@@ -266,6 +270,13 @@ var nullishCoalescingOperators_default = ruleCreator.createRule(typescriptLangua
|
|
|
266
270
|
text: `${leftText} ?? ${getText(alternate)}`
|
|
267
271
|
};
|
|
268
272
|
}
|
|
273
|
+
function createNullishAssignmentFix(left, right, sourceFile, range) {
|
|
274
|
+
const getText = (node) => sourceFile.text.substring(node.getStart(sourceFile), node.getEnd());
|
|
275
|
+
return {
|
|
276
|
+
range,
|
|
277
|
+
text: `${getText(left)} ??= ${getText(right)};`
|
|
278
|
+
};
|
|
279
|
+
}
|
|
269
280
|
return { visitors: {
|
|
270
281
|
BinaryExpression: (node, { options, sourceFile, typeChecker }) => {
|
|
271
282
|
if (options.ignoreConditionalTests && isConditionalTest(node) || options.ignoreMixedLogicalExpressions && isMixedLogicalExpression(node) || ![ts.SyntaxKind.BarBarEqualsToken, ts.SyntaxKind.BarBarToken].includes(node.operatorToken.kind) || shouldIgnoreNode(node.left, options.ignorePrimitives, typeChecker)) return;
|
|
@@ -292,12 +303,13 @@ var nullishCoalescingOperators_default = ruleCreator.createRule(typescriptLangua
|
|
|
292
303
|
});
|
|
293
304
|
},
|
|
294
305
|
IfStatement: (node, { options, sourceFile, typeChecker }) => {
|
|
295
|
-
|
|
306
|
+
const checkedValue = getIfStatementNullishCheckValue(node);
|
|
307
|
+
if (options.ignoreIfStatements || node.elseStatement || !checkedValue) return;
|
|
296
308
|
const assignmentExpression = extractAssignmentFromIfStatement(node);
|
|
297
|
-
if (!assignmentExpression || shouldIgnoreNode(assignmentExpression.left, options.ignorePrimitives, typeChecker)) return;
|
|
309
|
+
if (!assignmentExpression || !hasSameTokens(assignmentExpression.left, checkedValue, sourceFile) || shouldIgnoreNode(assignmentExpression.left, options.ignorePrimitives, typeChecker)) return;
|
|
298
310
|
const range = getTSNodeRange(node, sourceFile);
|
|
299
311
|
context.report({
|
|
300
|
-
fix:
|
|
312
|
+
fix: createNullishAssignmentFix(assignmentExpression.left, assignmentExpression.right, sourceFile, range),
|
|
301
313
|
message: "preferNullishAssignment",
|
|
302
314
|
range
|
|
303
315
|
});
|
|
@@ -1,20 +1,6 @@
|
|
|
1
1
|
import { ruleCreator } from "./ruleCreator.js";
|
|
2
|
-
import { getTSNodeRange, typescriptLanguage } from "@flint.fyi/typescript-language";
|
|
3
|
-
import ts from "typescript";
|
|
2
|
+
import { getScopeManager, getTSNodeRange, typescriptLanguage } from "@flint.fyi/typescript-language";
|
|
4
3
|
//#region src/rules/parameterReassignments.ts
|
|
5
|
-
function collectParameterNames(parameters) {
|
|
6
|
-
const names = /* @__PURE__ */ new Set();
|
|
7
|
-
const collectNames = (node) => {
|
|
8
|
-
if (node.kind === ts.SyntaxKind.Identifier) names.add(node.text);
|
|
9
|
-
else if (ts.isObjectBindingPattern(node)) {
|
|
10
|
-
for (const element of node.elements) if (!ts.isOmittedExpression(element)) collectNames(element.name);
|
|
11
|
-
} else if (ts.isArrayBindingPattern(node)) {
|
|
12
|
-
for (const element of node.elements) if (!ts.isOmittedExpression(element)) collectNames(element.name);
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
for (const parameter of parameters) collectNames(parameter.name);
|
|
16
|
-
return names;
|
|
17
|
-
}
|
|
18
4
|
var parameterReassignments_default = ruleCreator.createRule(typescriptLanguage, {
|
|
19
5
|
about: {
|
|
20
6
|
description: "Disallow reassignment of function parameters.",
|
|
@@ -31,42 +17,25 @@ var parameterReassignments_default = ruleCreator.createRule(typescriptLanguage,
|
|
|
31
17
|
suggestions: ["Use a new variable if you need to modify the value.", "Use a different pattern such as a helper function."]
|
|
32
18
|
} },
|
|
33
19
|
setup(context) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
currentNode = currentNode.parent;
|
|
20
|
+
function reportParameterReassignments(node, { sourceFile }) {
|
|
21
|
+
const scopeManager = getScopeManager(sourceFile);
|
|
22
|
+
const parameterVariables = new Set(node.parameters.flatMap((parameter) => scopeManager.getDeclaredVariables(parameter)));
|
|
23
|
+
for (const reference of scopeManager.getReferencesInScope(node)) {
|
|
24
|
+
if (!reference.isWrite || !reference.variable || !parameterVariables.has(reference.variable)) continue;
|
|
25
|
+
context.report({
|
|
26
|
+
message: "parameterReassignment",
|
|
27
|
+
range: getTSNodeRange(reference.identifier, sourceFile)
|
|
28
|
+
});
|
|
46
29
|
}
|
|
47
|
-
}
|
|
48
|
-
const handleUnaryExpression = (node, { sourceFile }) => {
|
|
49
|
-
if ((node.operator === ts.SyntaxKind.PlusPlusToken || node.operator === ts.SyntaxKind.MinusMinusToken) && ts.isIdentifier(node.operand)) checkParameterAssignment(node.operand.text, node.operand, sourceFile);
|
|
50
|
-
};
|
|
30
|
+
}
|
|
51
31
|
return { visitors: {
|
|
52
|
-
ArrowFunction:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
FunctionDeclaration: (node) => {
|
|
60
|
-
scopes.set(node, collectParameterNames(node.parameters));
|
|
61
|
-
},
|
|
62
|
-
FunctionExpression: (node) => {
|
|
63
|
-
scopes.set(node, collectParameterNames(node.parameters));
|
|
64
|
-
},
|
|
65
|
-
PostfixUnaryExpression: handleUnaryExpression,
|
|
66
|
-
PrefixUnaryExpression: handleUnaryExpression,
|
|
67
|
-
Program: () => {
|
|
68
|
-
scopes.clear();
|
|
69
|
-
}
|
|
32
|
+
ArrowFunction: reportParameterReassignments,
|
|
33
|
+
Constructor: reportParameterReassignments,
|
|
34
|
+
FunctionDeclaration: reportParameterReassignments,
|
|
35
|
+
FunctionExpression: reportParameterReassignments,
|
|
36
|
+
GetAccessor: reportParameterReassignments,
|
|
37
|
+
MethodDeclaration: reportParameterReassignments,
|
|
38
|
+
SetAccessor: reportParameterReassignments
|
|
70
39
|
} };
|
|
71
40
|
}
|
|
72
41
|
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
declare const _default: import("@flint.fyi/core").Rule<{
|
|
3
|
+
readonly description: "Restricts specified modules from being imported.";
|
|
4
|
+
readonly id: "restrictedImports";
|
|
5
|
+
} & {
|
|
6
|
+
readonly pluginId: string;
|
|
7
|
+
readonly url: string;
|
|
8
|
+
}, "restricted" | "moduleRestricted" | "moduleRestrictedWithMessage" | "namespaceRestricted" | "namespaceRestrictedWithMessage" | "restrictedWithMessage", {
|
|
9
|
+
restrictions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
10
|
+
allowTypeImports: z.ZodOptional<z.ZodBoolean>;
|
|
11
|
+
message: z.ZodOptional<z.ZodString>;
|
|
12
|
+
specifier: z.ZodUnion<readonly [z.ZodObject<{
|
|
13
|
+
from: z.ZodLiteral<"file">;
|
|
14
|
+
name: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
15
|
+
path: z.ZodOptional<z.ZodString>;
|
|
16
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
17
|
+
from: z.ZodLiteral<"lib">;
|
|
18
|
+
name: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
19
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
20
|
+
from: z.ZodLiteral<"package">;
|
|
21
|
+
name: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
22
|
+
package: z.ZodString;
|
|
23
|
+
}, z.core.$strip>]>;
|
|
24
|
+
}, z.core.$strip>>>;
|
|
25
|
+
}>;
|
|
26
|
+
export default _default;
|
|
27
|
+
//# sourceMappingURL=restrictedImports.d.ts.map
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { ruleCreator } from "./ruleCreator.js";
|
|
2
|
+
import { getSpecifierNames } from "../type-utils/getSpecifierNames.js";
|
|
3
|
+
import { matchesSpecifier } from "../type-utils/matchesSpecifier.js";
|
|
4
|
+
import { typeOrValueSpecifierSchema } from "../type-utils/schemas.js";
|
|
5
|
+
import { getTSNodeRange, typescriptLanguage } from "@flint.fyi/typescript-language";
|
|
6
|
+
import ts, { SyntaxKind } from "typescript";
|
|
7
|
+
import { z } from "zod/v4";
|
|
8
|
+
//#region src/rules/restrictedImports.ts
|
|
9
|
+
const restrictionSchema = z.object({
|
|
10
|
+
allowTypeImports: z.boolean().optional().describe("Whether type-only imports are exempt from the restriction."),
|
|
11
|
+
message: z.string().optional().describe("A custom message to display when the restriction is triggered."),
|
|
12
|
+
specifier: typeOrValueSpecifierSchema.describe("A TypeOrValueSpecifier identifying the restricted import.")
|
|
13
|
+
});
|
|
14
|
+
function resolveModuleDeclarations(moduleSpecifier, typeChecker) {
|
|
15
|
+
return typeChecker.getSymbolAtLocation(moduleSpecifier)?.getDeclarations();
|
|
16
|
+
}
|
|
17
|
+
function resolveSymbolDeclarations(nameNode, typeChecker) {
|
|
18
|
+
let symbol = typeChecker.getSymbolAtLocation(nameNode);
|
|
19
|
+
if (!symbol) return;
|
|
20
|
+
if (symbol.flags & ts.SymbolFlags.Alias) symbol = typeChecker.getAliasedSymbol(symbol);
|
|
21
|
+
return symbol.getDeclarations();
|
|
22
|
+
}
|
|
23
|
+
var restrictedImports_default = ruleCreator.createRule(typescriptLanguage, {
|
|
24
|
+
about: {
|
|
25
|
+
description: "Restricts specified modules from being imported.",
|
|
26
|
+
id: "restrictedImports"
|
|
27
|
+
},
|
|
28
|
+
messages: {
|
|
29
|
+
moduleRestricted: {
|
|
30
|
+
primary: "'{{ source }}' import is restricted.",
|
|
31
|
+
secondary: ["This module has been restricted by project configuration."],
|
|
32
|
+
suggestions: ["Remove this import or replace it with an allowed alternative."]
|
|
33
|
+
},
|
|
34
|
+
moduleRestrictedWithMessage: {
|
|
35
|
+
primary: "'{{ source }}' import is restricted. {{ customMessage }}",
|
|
36
|
+
secondary: ["This module has been restricted by project configuration."],
|
|
37
|
+
suggestions: ["Remove this import or replace it with an allowed alternative."]
|
|
38
|
+
},
|
|
39
|
+
namespaceRestricted: {
|
|
40
|
+
primary: "* import is invalid because '{{ restrictedNames }}' from '{{ source }}' is restricted.",
|
|
41
|
+
secondary: ["This import uses a namespace or wildcard import, but specific names from this module are restricted."],
|
|
42
|
+
suggestions: ["Replace the namespace import with named imports that are allowed."]
|
|
43
|
+
},
|
|
44
|
+
namespaceRestrictedWithMessage: {
|
|
45
|
+
primary: "* import is invalid because '{{ restrictedNames }}' from '{{ source }}' is restricted. {{ customMessage }}",
|
|
46
|
+
secondary: ["This import uses a namespace or wildcard import, but specific names from this module are restricted."],
|
|
47
|
+
suggestions: ["Replace the namespace import with named imports that are allowed."]
|
|
48
|
+
},
|
|
49
|
+
restricted: {
|
|
50
|
+
primary: "'{{ importName }}' import from '{{ source }}' is restricted.",
|
|
51
|
+
secondary: ["This import name has been restricted by project configuration."],
|
|
52
|
+
suggestions: ["Remove this import or replace it with an allowed alternative."]
|
|
53
|
+
},
|
|
54
|
+
restrictedWithMessage: {
|
|
55
|
+
primary: "'{{ importName }}' import from '{{ source }}' is restricted. {{ customMessage }}",
|
|
56
|
+
secondary: ["This import name has been restricted by project configuration."],
|
|
57
|
+
suggestions: ["Remove this import or replace it with an allowed alternative."]
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
options: { restrictions: z.array(restrictionSchema).default([]).describe("Restrictions on imports, using TypeOrValueSpecifier to identify targets.") },
|
|
61
|
+
setup(context) {
|
|
62
|
+
function checkNamedRestrictions(restrictions, declarations, importedName, isTypeOnly, source, range, program) {
|
|
63
|
+
for (const restriction of restrictions) {
|
|
64
|
+
if (restriction.allowTypeImports && isTypeOnly) continue;
|
|
65
|
+
if (matchesSpecifier(importedName, declarations, restriction.specifier, program)) context.report({
|
|
66
|
+
data: {
|
|
67
|
+
customMessage: restriction.message ?? "",
|
|
68
|
+
importName: importedName,
|
|
69
|
+
source
|
|
70
|
+
},
|
|
71
|
+
message: restriction.message ? "restrictedWithMessage" : "restricted",
|
|
72
|
+
range
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function checkWildcardRestrictions(restrictions, moduleDeclarations, source, topLevelTypeOnly, range, program) {
|
|
77
|
+
for (const restriction of restrictions) {
|
|
78
|
+
if (restriction.allowTypeImports && topLevelTypeOnly) continue;
|
|
79
|
+
const names = getSpecifierNames(restriction.specifier);
|
|
80
|
+
if (names !== void 0) {
|
|
81
|
+
if (matchesSpecifier(void 0, moduleDeclarations, {
|
|
82
|
+
...restriction.specifier,
|
|
83
|
+
name: void 0
|
|
84
|
+
}, program)) context.report({
|
|
85
|
+
data: {
|
|
86
|
+
customMessage: restriction.message ?? "",
|
|
87
|
+
restrictedNames: names.join("', '"),
|
|
88
|
+
source
|
|
89
|
+
},
|
|
90
|
+
message: restriction.message ? "namespaceRestrictedWithMessage" : "namespaceRestricted",
|
|
91
|
+
range
|
|
92
|
+
});
|
|
93
|
+
} else if (matchesSpecifier(void 0, moduleDeclarations, restriction.specifier, program)) context.report({
|
|
94
|
+
data: {
|
|
95
|
+
customMessage: restriction.message ?? "",
|
|
96
|
+
source
|
|
97
|
+
},
|
|
98
|
+
message: restriction.message ? "moduleRestrictedWithMessage" : "moduleRestricted",
|
|
99
|
+
range
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return { visitors: {
|
|
104
|
+
ExportDeclaration: (node, { options, program, sourceFile, typeChecker }) => {
|
|
105
|
+
if (!node.moduleSpecifier || !ts.isStringLiteral(node.moduleSpecifier)) return;
|
|
106
|
+
const source = node.moduleSpecifier.text;
|
|
107
|
+
const topLevelTypeOnly = node.isTypeOnly;
|
|
108
|
+
const range = getTSNodeRange(node, sourceFile);
|
|
109
|
+
if (node.exportClause && ts.isNamedExports(node.exportClause)) for (const element of node.exportClause.elements) {
|
|
110
|
+
const isTypeOnly = topLevelTypeOnly || element.isTypeOnly;
|
|
111
|
+
const importedName = element.propertyName ? element.propertyName.text : element.name.text;
|
|
112
|
+
const declarations = resolveSymbolDeclarations(element.name, typeChecker);
|
|
113
|
+
if (!declarations?.length) continue;
|
|
114
|
+
checkNamedRestrictions(options.restrictions, declarations, importedName, isTypeOnly, source, range, program);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
const moduleDeclarations = resolveModuleDeclarations(node.moduleSpecifier, typeChecker);
|
|
118
|
+
if (!moduleDeclarations?.length) return;
|
|
119
|
+
checkWildcardRestrictions(options.restrictions, moduleDeclarations, source, topLevelTypeOnly, range, program);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
ImportDeclaration: (node, { options, program, sourceFile, typeChecker }) => {
|
|
123
|
+
if (!ts.isStringLiteral(node.moduleSpecifier)) return;
|
|
124
|
+
const source = node.moduleSpecifier.text;
|
|
125
|
+
const range = getTSNodeRange(node, sourceFile);
|
|
126
|
+
if (!node.importClause) {
|
|
127
|
+
const moduleDeclarations = resolveModuleDeclarations(node.moduleSpecifier, typeChecker);
|
|
128
|
+
if (!moduleDeclarations?.length) return;
|
|
129
|
+
for (const restriction of options.restrictions) if (matchesSpecifier(void 0, moduleDeclarations, restriction.specifier, program)) context.report({
|
|
130
|
+
data: {
|
|
131
|
+
customMessage: restriction.message ?? "",
|
|
132
|
+
source
|
|
133
|
+
},
|
|
134
|
+
message: restriction.message ? "moduleRestrictedWithMessage" : "moduleRestricted",
|
|
135
|
+
range
|
|
136
|
+
});
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const topLevelTypeOnly = node.importClause.phaseModifier === SyntaxKind.TypeKeyword;
|
|
140
|
+
if (node.importClause.name) {
|
|
141
|
+
const declarations = resolveSymbolDeclarations(node.importClause.name, typeChecker);
|
|
142
|
+
if (declarations?.length) checkNamedRestrictions(options.restrictions, declarations, "default", topLevelTypeOnly, source, range, program);
|
|
143
|
+
}
|
|
144
|
+
const bindings = node.importClause.namedBindings;
|
|
145
|
+
if (!bindings) return;
|
|
146
|
+
if (ts.isNamedImports(bindings)) {
|
|
147
|
+
for (const element of bindings.elements) {
|
|
148
|
+
const isTypeOnly = topLevelTypeOnly || element.isTypeOnly;
|
|
149
|
+
const importedName = element.propertyName ? element.propertyName.text : element.name.text;
|
|
150
|
+
const declarations = resolveSymbolDeclarations(element.name, typeChecker);
|
|
151
|
+
if (!declarations?.length) continue;
|
|
152
|
+
checkNamedRestrictions(options.restrictions, declarations, importedName, isTypeOnly, source, range, program);
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const moduleDeclarations = resolveModuleDeclarations(node.moduleSpecifier, typeChecker);
|
|
157
|
+
if (!moduleDeclarations?.length) return;
|
|
158
|
+
checkWildcardRestrictions(options.restrictions, moduleDeclarations, source, topLevelTypeOnly, range, program);
|
|
159
|
+
}
|
|
160
|
+
} };
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
//#endregion
|
|
164
|
+
export { restrictedImports_default as default };
|
|
165
|
+
|
|
166
|
+
//# sourceMappingURL=restrictedImports.js.map
|
package/lib/rules/typeImports.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ruleCreator } from "./ruleCreator.js";
|
|
2
|
-
import { forEachChild } from "
|
|
3
|
-
import { getTSNodeRange, typescriptLanguage } from "@flint.fyi/typescript-language";
|
|
2
|
+
import { forEachChild, getTSNodeRange, typescriptLanguage } from "@flint.fyi/typescript-language";
|
|
4
3
|
import ts, { SyntaxKind } from "typescript";
|
|
5
4
|
import { z } from "zod/v4";
|
|
6
5
|
//#region src/rules/typeImports.ts
|
|
@@ -14,13 +14,10 @@ var unassignedVariables_default = ruleCreator.createRule(typescriptLanguage, {
|
|
|
14
14
|
suggestions: ["Assign a value to the variable, either at declaration or before it is used.", "If the variable is meant to be `undefined`, consider making that explicit for clarity."]
|
|
15
15
|
} },
|
|
16
16
|
setup(context) {
|
|
17
|
-
|
|
18
|
-
return !!getModifyingReferences(identifier, sourceFile, typeChecker).length;
|
|
19
|
-
}
|
|
20
|
-
return { visitors: { VariableDeclaration: (node, { sourceFile, typeChecker }) => {
|
|
17
|
+
return { visitors: { VariableDeclaration: (node, { sourceFile }) => {
|
|
21
18
|
if (node.initializer || node.name.kind !== SyntaxKind.Identifier) return;
|
|
22
19
|
if (node.parent.kind === SyntaxKind.VariableDeclarationList && !!(node.parent.flags & ts.NodeFlags.Const)) return;
|
|
23
|
-
if (!
|
|
20
|
+
if (!getModifyingReferences(node.name, sourceFile).length) context.report({
|
|
24
21
|
data: { name: node.name.text },
|
|
25
22
|
message: "noUnassigned",
|
|
26
23
|
range: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { AST } from "@flint.fyi/typescript-language";
|
|
2
1
|
import { SyntaxKind } from "typescript";
|
|
2
|
+
import { type AST } from "@flint.fyi/typescript-language";
|
|
3
3
|
export type EqualityOperator = "!=" | "!==" | "==" | "===";
|
|
4
4
|
export declare function isNullishLiteral(node: AST.Expression): boolean;
|
|
5
5
|
export declare function isUndefinedIdentifier(node: AST.Expression): boolean;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Checker } from "@flint.fyi/typescript-language";
|
|
2
1
|
import type ts from "typescript";
|
|
2
|
+
import type { Checker } from "@flint.fyi/typescript-language";
|
|
3
3
|
export declare function formatReportedType(type: ts.Type, typeChecker: Checker): string;
|
|
4
4
|
//# sourceMappingURL=formatReportedType.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type AST, type TypeScriptFileServices } from "@flint.fyi/typescript-language";
|
|
2
1
|
import ts from "typescript";
|
|
2
|
+
import { type AST, type TypeScriptFileServices } from "@flint.fyi/typescript-language";
|
|
3
3
|
export declare function getRegExpConstruction(node: AST.CallExpression | AST.NewExpression, { sourceFile, typeChecker }: TypeScriptFileServices): {
|
|
4
4
|
args: ts.NodeArray<AST.Expression>;
|
|
5
5
|
flags: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AST } from "@flint.fyi/typescript-language";
|
|
2
1
|
import * as ts from "typescript";
|
|
2
|
+
import type { AST } from "@flint.fyi/typescript-language";
|
|
3
3
|
export declare function isDirectEqualityCheck(node: AST.ArrowFunction | AST.FunctionExpression, operators: ts.SyntaxKind[], parameterName: string): AST.Expression | undefined;
|
|
4
4
|
//# sourceMappingURL=isDirectEqualityCheck.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/type-utils/getSpecifierNames.ts
|
|
2
|
+
function getSpecifierNames(specifier) {
|
|
3
|
+
if (specifier.name === void 0) return;
|
|
4
|
+
return Array.isArray(specifier.name) ? specifier.name : [specifier.name];
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
export { getSpecifierNames };
|
|
8
|
+
|
|
9
|
+
//# sourceMappingURL=getSpecifierNames.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
//#region src/type-utils/isDeclaredInModuleBlock.ts
|
|
3
|
+
function isDeclaredInModuleBlock(declaration, packageName) {
|
|
4
|
+
let current = declaration;
|
|
5
|
+
while (!ts.isSourceFile(current)) {
|
|
6
|
+
if (ts.isModuleDeclaration(current) && !(current.flags & ts.NodeFlags.Namespace) && ts.isStringLiteral(current.name) && current.name.text === packageName) return true;
|
|
7
|
+
current = current.parent;
|
|
8
|
+
}
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { isDeclaredInModuleBlock };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=isDeclaredInModuleBlock.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { pathKey } from "@flint.fyi/utils";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
//#region src/type-utils/isFromFile.ts
|
|
5
|
+
function isFromFile(sourceFile, specifiedPath, program) {
|
|
6
|
+
if (specifiedPath === void 0) return !sourceFile.fileName.includes("/node_modules/") && !program.isSourceFileDefaultLibrary(sourceFile);
|
|
7
|
+
const caseSensitive = ts.sys.useCaseSensitiveFileNames;
|
|
8
|
+
return pathKey(sourceFile.fileName, caseSensitive) === pathKey(path.resolve(program.getCurrentDirectory(), specifiedPath), caseSensitive);
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { isFromFile };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=isFromFile.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { isDeclaredInModuleBlock } from "./isDeclaredInModuleBlock.js";
|
|
2
|
+
import "typescript";
|
|
3
|
+
//#region src/type-utils/isFromPackage.ts
|
|
4
|
+
function isFromPackage(declaration, packageName, program) {
|
|
5
|
+
if (isDeclaredInModuleBlock(declaration, packageName)) return true;
|
|
6
|
+
const sourceFile = declaration.getSourceFile();
|
|
7
|
+
if (!program.isSourceFileFromExternalLibrary(sourceFile)) return false;
|
|
8
|
+
const resolvedName = program.sourceFileToPackageName.get(sourceFile.path);
|
|
9
|
+
if (resolvedName === packageName || sourceFile.fileName.includes(`/node_modules/${packageName}/`)) return true;
|
|
10
|
+
const typesPackageName = packageName.replace(/^@([^/]+)\//, "$1__");
|
|
11
|
+
return resolvedName === typesPackageName || sourceFile.fileName.includes(`/node_modules/@types/${typesPackageName}/`);
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { isFromPackage };
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=isFromPackage.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type ts from "typescript";
|
|
2
|
+
import type { TypeOrValueSpecifier } from "./schemas.ts";
|
|
3
|
+
export declare function matchesSpecifier(importedName: string | undefined, declarations: ts.Declaration[], specifier: TypeOrValueSpecifier, program: ts.Program): boolean;
|
|
4
|
+
//# sourceMappingURL=matchesSpecifier.d.ts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getSpecifierNames } from "./getSpecifierNames.js";
|
|
2
|
+
import { isFromFile } from "./isFromFile.js";
|
|
3
|
+
import { isFromPackage } from "./isFromPackage.js";
|
|
4
|
+
import { declarationIncludesGlobal } from "@flint.fyi/typescript-language";
|
|
5
|
+
//#region src/type-utils/matchesSpecifier.ts
|
|
6
|
+
function matchesSpecifier(importedName, declarations, specifier, program) {
|
|
7
|
+
const names = getSpecifierNames(specifier);
|
|
8
|
+
if (names !== void 0 && (importedName === void 0 || !names.includes(importedName))) return false;
|
|
9
|
+
return declarations.some((declaration) => {
|
|
10
|
+
switch (specifier.from) {
|
|
11
|
+
case "file": return isFromFile(declaration.getSourceFile(), specifier.path, program);
|
|
12
|
+
case "lib": return declarationIncludesGlobal(declaration);
|
|
13
|
+
case "package": return isFromPackage(declaration, specifier.package, program);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { matchesSpecifier };
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=matchesSpecifier.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
export declare const typeOrValueSpecifierSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
3
|
+
from: z.ZodLiteral<"file">;
|
|
4
|
+
name: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
5
|
+
path: z.ZodOptional<z.ZodString>;
|
|
6
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
7
|
+
from: z.ZodLiteral<"lib">;
|
|
8
|
+
name: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
9
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
10
|
+
from: z.ZodLiteral<"package">;
|
|
11
|
+
name: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
12
|
+
package: z.ZodString;
|
|
13
|
+
}, z.core.$strip>]>;
|
|
14
|
+
export type TypeOrValueSpecifier = z.infer<typeof typeOrValueSpecifierSchema>;
|
|
15
|
+
//# sourceMappingURL=schemas.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
//#region src/type-utils/schemas.ts
|
|
3
|
+
const fileSpecifierSchema = z.object({
|
|
4
|
+
from: z.literal("file"),
|
|
5
|
+
name: z.union([z.string(), z.array(z.string())]).optional(),
|
|
6
|
+
path: z.string().optional()
|
|
7
|
+
});
|
|
8
|
+
const libSpecifierSchema = z.object({
|
|
9
|
+
from: z.literal("lib"),
|
|
10
|
+
name: z.union([z.string(), z.array(z.string())]).optional()
|
|
11
|
+
});
|
|
12
|
+
const packageSpecifierSchema = z.object({
|
|
13
|
+
from: z.literal("package"),
|
|
14
|
+
name: z.union([z.string(), z.array(z.string())]).optional(),
|
|
15
|
+
package: z.string()
|
|
16
|
+
});
|
|
17
|
+
const typeOrValueSpecifierSchema = z.union([
|
|
18
|
+
fileSpecifierSchema,
|
|
19
|
+
libSpecifierSchema,
|
|
20
|
+
packageSpecifierSchema
|
|
21
|
+
]);
|
|
22
|
+
//#endregion
|
|
23
|
+
export { typeOrValueSpecifierSchema };
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=schemas.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flint.fyi/ts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "[Experimental] TypeScript language plugin for Flint.",
|
|
5
5
|
"homepage": "https://flint.fyi/rules/ts",
|
|
6
6
|
"repository": {
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
},
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"author": {
|
|
13
|
-
"name": "
|
|
14
|
-
"
|
|
13
|
+
"name": "Flint Team",
|
|
14
|
+
"url": "https://flint.fyi/team"
|
|
15
15
|
},
|
|
16
16
|
"sideEffects": false,
|
|
17
17
|
"type": "module",
|
|
@@ -29,14 +29,14 @@
|
|
|
29
29
|
"ts-api-utils": "^2.4.0",
|
|
30
30
|
"typescript": "^5.9.3 || ^6.0.0",
|
|
31
31
|
"zod": "^4.3.6",
|
|
32
|
-
"@flint.fyi/core": "^0.
|
|
32
|
+
"@flint.fyi/core": "^0.23.0",
|
|
33
33
|
"@flint.fyi/utils": "^0.14.1",
|
|
34
|
-
"@flint.fyi/typescript-language": "^0.18.
|
|
34
|
+
"@flint.fyi/typescript-language": "^0.18.3"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"tsdown": "0.21.0",
|
|
38
38
|
"vitest": "4.1.0",
|
|
39
|
-
"@flint.fyi/rule-tester": "^0.16.
|
|
39
|
+
"@flint.fyi/rule-tester": "^0.16.4"
|
|
40
40
|
},
|
|
41
41
|
"engines": {
|
|
42
42
|
"node": ">=24.0.0"
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { AST } from "@flint.fyi/typescript-language";
|
|
2
|
-
import ts from "typescript";
|
|
3
|
-
export declare const forEachChild: <T>(node: AST.AnyNode, cbNode: (node: AST.AnyNode) => T | undefined, cbNodes?: (nodes: ts.NodeArray<AST.AnyNode>) => T | undefined) => T | undefined;
|
|
4
|
-
//# sourceMappingURL=forEachChild.d.ts.map
|