@totto2727/oxlint 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +85 -2
- package/THIRD-PARTY-NOTICES.md +48 -0
- package/dist/effect.d.ts +4 -0
- package/dist/effect.js +6 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.js +1613 -0
- package/dist/preset-builder-Qxb4CeX1.js +666 -0
- package/dist/preset.d.ts +4 -0
- package/dist/preset.js +6 -0
- package/dist/rule-groups-jUlABfyW.js +43 -0
- package/dist/typescript.d.ts +4 -0
- package/dist/typescript.js +6 -0
- package/package.json +52 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,1613 @@
|
|
|
1
|
+
import { n as effectRuleNames, r as typescriptRuleNames, t as compatibilityRuleNames } from "./rule-groups-jUlABfyW.js";
|
|
2
|
+
import { Predicate, Schema, String } from "effect";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import ts from "typescript-api";
|
|
6
|
+
//#region src/helpers.ts
|
|
7
|
+
const Reportable = Schema.Struct({ range: Schema.mutable(Schema.Tuple([Schema.Number, Schema.Number])) });
|
|
8
|
+
const { hasProperty } = Predicate;
|
|
9
|
+
const isReportable = Schema.is(Reportable);
|
|
10
|
+
const isEqualityOperator = (op) => op === "==" || op === "!=" || op === "===" || op === "!==";
|
|
11
|
+
const getLiteralNumber = (node) => {
|
|
12
|
+
if (Predicate.isObject(node) && node["type"] === "Literal" && Predicate.isNumber(node["value"])) return node["value"];
|
|
13
|
+
return null;
|
|
14
|
+
};
|
|
15
|
+
const isSchemaMethodCall = (node, methodName) => {
|
|
16
|
+
if (Predicate.isObject(node) && node["type"] === "CallExpression" && Predicate.isObject(node["callee"]) && node["callee"]["type"] === "MemberExpression" && !Predicate.isTruthy(node["callee"]["computed"]) && Predicate.isObject(node["callee"]["object"]) && node["callee"]["object"]["type"] === "Identifier" && node["callee"]["object"]["name"] === "Schema" && Predicate.isObject(node["callee"]["property"]) && node["callee"]["property"]["type"] === "Identifier" && node["callee"]["property"]["name"] === methodName) return true;
|
|
17
|
+
return false;
|
|
18
|
+
};
|
|
19
|
+
const getImportSource = (node) => {
|
|
20
|
+
if (Predicate.isObject(node) && hasProperty(node, "source") && Predicate.isObject(node["source"]) && hasProperty(node["source"], "value") && Predicate.isString(node["source"]["value"])) return node["source"]["value"];
|
|
21
|
+
return null;
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/rules/force-ts-extension.ts
|
|
25
|
+
/** Bare specifiers and absolute paths are outside this rule family's scope. */
|
|
26
|
+
const matchImportPath = (value) => {
|
|
27
|
+
const start = /^(\.+|#(?!#)[^/?#]*)(?=\/)/u.exec(value)?.[1];
|
|
28
|
+
if (start === void 0) return null;
|
|
29
|
+
const remainder = value.slice(start.length);
|
|
30
|
+
const suffixIndex = remainder.search(/[?#]/u);
|
|
31
|
+
const pathname = suffixIndex === -1 ? remainder : remainder.slice(0, suffixIndex);
|
|
32
|
+
const suffix = suffixIndex === -1 ? "" : remainder.slice(suffixIndex);
|
|
33
|
+
const basename = pathname.slice(pathname.lastIndexOf("/") + 1);
|
|
34
|
+
const dot = basename.lastIndexOf(".");
|
|
35
|
+
return {
|
|
36
|
+
start,
|
|
37
|
+
path: pathname,
|
|
38
|
+
suffix,
|
|
39
|
+
extension: dot > 0 ? basename.slice(dot) : "",
|
|
40
|
+
directory: basename === "" || basename === "." || basename === ".."
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
const importExtensionSchema = [{
|
|
44
|
+
additionalProperties: false,
|
|
45
|
+
properties: { mode: {
|
|
46
|
+
enum: ["js", "ts"],
|
|
47
|
+
type: "string"
|
|
48
|
+
} },
|
|
49
|
+
type: "object"
|
|
50
|
+
}];
|
|
51
|
+
const getImportExtensionMode = (option) => typeof option === "object" && option !== null && "mode" in option && option.mode === "js" ? "js" : "ts";
|
|
52
|
+
const extensionTargets = {
|
|
53
|
+
".js": {
|
|
54
|
+
js: ".js",
|
|
55
|
+
ts: ".ts"
|
|
56
|
+
},
|
|
57
|
+
".jsx": {
|
|
58
|
+
js: ".js",
|
|
59
|
+
ts: ".tsx"
|
|
60
|
+
},
|
|
61
|
+
".ts": {
|
|
62
|
+
js: ".js",
|
|
63
|
+
ts: ".ts"
|
|
64
|
+
},
|
|
65
|
+
".tsx": {
|
|
66
|
+
js: ".js",
|
|
67
|
+
ts: ".tsx"
|
|
68
|
+
},
|
|
69
|
+
".mjs": {
|
|
70
|
+
js: ".mjs",
|
|
71
|
+
ts: ".mts"
|
|
72
|
+
},
|
|
73
|
+
".mts": {
|
|
74
|
+
js: ".mjs",
|
|
75
|
+
ts: ".mts"
|
|
76
|
+
},
|
|
77
|
+
".cjs": {
|
|
78
|
+
js: ".cjs",
|
|
79
|
+
ts: ".cts"
|
|
80
|
+
},
|
|
81
|
+
".cts": {
|
|
82
|
+
js: ".cjs",
|
|
83
|
+
ts: ".cts"
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
/** Preserve explicit ESM/CommonJS families while normalizing code extensions. */
|
|
87
|
+
const normalizeImportExtension = (value, mode) => {
|
|
88
|
+
const match = matchImportPath(value);
|
|
89
|
+
if (match === null) return null;
|
|
90
|
+
const targets = extensionTargets[match.extension];
|
|
91
|
+
if (targets === void 0) return null;
|
|
92
|
+
const extension = targets[mode];
|
|
93
|
+
if (extension === match.extension) return null;
|
|
94
|
+
return `${match.start}${match.path.slice(0, -match.extension.length)}${extension}${match.suffix}`;
|
|
95
|
+
};
|
|
96
|
+
const quoteLiteral = (value, quote) => {
|
|
97
|
+
const encoded = JSON.stringify(value);
|
|
98
|
+
if (quote === "'") return `'${encoded.slice(1, -1).replace(/\\"/gu, "\"").replace(/'/gu, "\\'")}'`;
|
|
99
|
+
return encoded;
|
|
100
|
+
};
|
|
101
|
+
/** Shared import boundary: null ignores, unchanged text reports without an unsafe fix. */
|
|
102
|
+
const createImportExtensionListeners = (context, replacement, message) => {
|
|
103
|
+
const check = (node) => {
|
|
104
|
+
if (!Predicate.isObject(node) || !Predicate.isObject(node["source"])) return;
|
|
105
|
+
const source = node["source"];
|
|
106
|
+
if (source["type"] !== "Literal" || typeof source["value"] !== "string" || typeof source["raw"] !== "string" || !isReportable(source)) return;
|
|
107
|
+
const fixed = replacement(source["value"]);
|
|
108
|
+
if (fixed === null) return;
|
|
109
|
+
const quote = source["raw"][0] ?? "\"";
|
|
110
|
+
context.report({
|
|
111
|
+
...fixed === source["value"] ? {} : { fix: (fixer) => fixer.replaceText(source, quoteLiteral(fixed, quote)) },
|
|
112
|
+
message,
|
|
113
|
+
node: source
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
return {
|
|
117
|
+
ExportAllDeclaration: check,
|
|
118
|
+
ExportNamedDeclaration: check,
|
|
119
|
+
ImportDeclaration: check,
|
|
120
|
+
ImportExpression: check
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
const rule$33 = {
|
|
124
|
+
create(context) {
|
|
125
|
+
const mode = getImportExtensionMode(context.options[0]);
|
|
126
|
+
return createImportExtensionListeners(context, (value) => {
|
|
127
|
+
const match = matchImportPath(value);
|
|
128
|
+
if (match !== null && (match.extension === "" || match.directory)) return value;
|
|
129
|
+
return normalizeImportExtension(value, mode);
|
|
130
|
+
}, mode === "js" ? "Use explicit JavaScript import extensions (.js, .mjs or .cjs) for code modules" : "Use explicit TypeScript import extensions (.ts, .tsx, .mts or .cts) for code modules");
|
|
131
|
+
},
|
|
132
|
+
meta: {
|
|
133
|
+
fixable: "code",
|
|
134
|
+
schema: importExtensionSchema,
|
|
135
|
+
type: "problem"
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/rules/consistent-import-extension.ts
|
|
140
|
+
const rule$32 = {
|
|
141
|
+
create(context) {
|
|
142
|
+
const mode = getImportExtensionMode(context.options[0]);
|
|
143
|
+
return createImportExtensionListeners(context, (value) => normalizeImportExtension(value, mode), mode === "js" ? "Use JavaScript import extensions (.js, .mjs or .cjs) for code modules" : "Use TypeScript import extensions (.ts, .tsx, .mts or .cts) for code modules");
|
|
144
|
+
},
|
|
145
|
+
meta: {
|
|
146
|
+
fixable: "code",
|
|
147
|
+
schema: importExtensionSchema,
|
|
148
|
+
type: "problem"
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/rules/force-array-empty.ts
|
|
153
|
+
const isLengthMember = (node) => Predicate.isObject(node) && node["type"] === "MemberExpression" && node["computed"] !== true && Predicate.isObject(node["property"]) && node["property"]["type"] === "Identifier" && node["property"]["name"] === "length";
|
|
154
|
+
const classifyAgainstZero = (operator, literalPosition) => {
|
|
155
|
+
if (operator === "===" || operator === "==") return "empty";
|
|
156
|
+
if (operator === "!==" || operator === "!=") return "non-empty";
|
|
157
|
+
if (operator === ">") return literalPosition === "right" ? "non-empty" : null;
|
|
158
|
+
if (operator === "<") return literalPosition === "right" ? null : "non-empty";
|
|
159
|
+
if (operator === ">=") return literalPosition === "right" ? null : "empty";
|
|
160
|
+
if (operator === "<=") return literalPosition === "right" ? "empty" : null;
|
|
161
|
+
return null;
|
|
162
|
+
};
|
|
163
|
+
const classifyAgainstOne = (operator, literalPosition) => {
|
|
164
|
+
if (operator === "<") return literalPosition === "right" ? "empty" : null;
|
|
165
|
+
if (operator === ">=") return literalPosition === "right" ? "non-empty" : null;
|
|
166
|
+
if (operator === ">") return literalPosition === "right" ? null : "empty";
|
|
167
|
+
if (operator === "<=") return literalPosition === "right" ? null : "non-empty";
|
|
168
|
+
return null;
|
|
169
|
+
};
|
|
170
|
+
const classifyLengthComparison = (operator, literalPosition, literal) => {
|
|
171
|
+
if (literal === 0) return classifyAgainstZero(operator, literalPosition);
|
|
172
|
+
if (literal === 1) return classifyAgainstOne(operator, literalPosition);
|
|
173
|
+
return null;
|
|
174
|
+
};
|
|
175
|
+
const rule$31 = {
|
|
176
|
+
create(context) {
|
|
177
|
+
return { BinaryExpression(node) {
|
|
178
|
+
if (!isReportable(node)) return;
|
|
179
|
+
if (!Predicate.isObject(node)) return;
|
|
180
|
+
if (!Predicate.isString(node["operator"])) return;
|
|
181
|
+
const leftIsLength = isLengthMember(node["left"]);
|
|
182
|
+
const rightIsLength = isLengthMember(node["right"]);
|
|
183
|
+
if (!leftIsLength && !rightIsLength) return;
|
|
184
|
+
const literalPosition = leftIsLength ? "right" : "left";
|
|
185
|
+
const literalSide = literalPosition === "right" ? node["right"] : node["left"];
|
|
186
|
+
const literal = getLiteralNumber(literalSide);
|
|
187
|
+
if (Predicate.isNullish(literal)) return;
|
|
188
|
+
const kind = classifyLengthComparison(node["operator"], literalPosition, literal);
|
|
189
|
+
if (Predicate.isNullish(kind)) return;
|
|
190
|
+
const message = kind === "empty" ? "Use Array.isArrayEmpty()/Array.isReadonlyArrayEmpty() instead of .length comparison" : "Use Array.isArrayNonEmpty()/Array.isReadonlyArrayNonEmpty() instead of .length comparison";
|
|
191
|
+
context.report({
|
|
192
|
+
message,
|
|
193
|
+
node
|
|
194
|
+
});
|
|
195
|
+
} };
|
|
196
|
+
},
|
|
197
|
+
meta: { type: "problem" }
|
|
198
|
+
};
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region src/rules/force-iterable-empty.ts
|
|
201
|
+
const isIterableSizeCall = (node) => Predicate.isObject(node) && node["type"] === "CallExpression" && Predicate.isObject(node["callee"]) && node["callee"]["type"] === "MemberExpression" && node["callee"]["computed"] !== true && Predicate.isObject(node["callee"]["object"]) && node["callee"]["object"]["type"] === "Identifier" && node["callee"]["object"]["name"] === "Iterable" && Predicate.isObject(node["callee"]["property"]) && node["callee"]["property"]["type"] === "Identifier" && node["callee"]["property"]["name"] === "size";
|
|
202
|
+
const rule$30 = {
|
|
203
|
+
create(context) {
|
|
204
|
+
return { BinaryExpression(node) {
|
|
205
|
+
if (!isReportable(node)) return;
|
|
206
|
+
if (!Predicate.isObject(node)) return;
|
|
207
|
+
if (!Predicate.isString(node["operator"])) return;
|
|
208
|
+
const leftIsSize = isIterableSizeCall(node["left"]);
|
|
209
|
+
const rightIsSize = isIterableSizeCall(node["right"]);
|
|
210
|
+
if (!leftIsSize && !rightIsSize) return;
|
|
211
|
+
const literalPosition = leftIsSize ? "right" : "left";
|
|
212
|
+
const literalSide = literalPosition === "right" ? node["right"] : node["left"];
|
|
213
|
+
const literal = getLiteralNumber(literalSide);
|
|
214
|
+
if (Predicate.isNullish(literal)) return;
|
|
215
|
+
const kind = classifyLengthComparison(node["operator"], literalPosition, literal);
|
|
216
|
+
if (Predicate.isNullish(kind)) return;
|
|
217
|
+
const message = kind === "empty" ? "Use Iterable.isEmpty() instead of Iterable.size() === 0 comparison" : "Use !Iterable.isEmpty() instead of Iterable.size() comparison";
|
|
218
|
+
context.report({
|
|
219
|
+
message,
|
|
220
|
+
node
|
|
221
|
+
});
|
|
222
|
+
} };
|
|
223
|
+
},
|
|
224
|
+
meta: { type: "problem" }
|
|
225
|
+
};
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region src/rules/force-predicate.ts
|
|
228
|
+
const typeofToPredicateMap = {
|
|
229
|
+
bigint: "Predicate.isBigInt",
|
|
230
|
+
boolean: "Predicate.isBoolean",
|
|
231
|
+
function: "Predicate.isFunction",
|
|
232
|
+
number: "Predicate.isNumber",
|
|
233
|
+
object: "Predicate.isObject",
|
|
234
|
+
string: "Predicate.isString",
|
|
235
|
+
symbol: "Predicate.isSymbol",
|
|
236
|
+
undefined: "Predicate.isUndefined"
|
|
237
|
+
};
|
|
238
|
+
const isNullLiteral = (node) => Predicate.isObject(node) && node["type"] === "Literal" && Predicate.isNullish(node["value"]);
|
|
239
|
+
const isUndefinedIdentifier = (node) => Predicate.isObject(node) && node["type"] === "Identifier" && node["name"] === "undefined";
|
|
240
|
+
const getNullishSide = (node) => {
|
|
241
|
+
if (isNullLiteral(node)) return "null";
|
|
242
|
+
if (isUndefinedIdentifier(node)) return "undefined";
|
|
243
|
+
return null;
|
|
244
|
+
};
|
|
245
|
+
const getTypeofType = (node) => {
|
|
246
|
+
if (Predicate.isObject(node) && node["type"] === "UnaryExpression" && node["operator"] === "typeof") return "typeof";
|
|
247
|
+
return null;
|
|
248
|
+
};
|
|
249
|
+
const getStringValue = (node) => {
|
|
250
|
+
if (Predicate.isObject(node) && node["type"] === "Literal" && Predicate.isString(node["value"])) return node["value"];
|
|
251
|
+
return null;
|
|
252
|
+
};
|
|
253
|
+
const resolveTypeStr = (leftTypeof, rightTypeof, leftStr, rightStr) => {
|
|
254
|
+
if (Predicate.isNotNullish(leftTypeof)) return rightStr;
|
|
255
|
+
if (Predicate.isNotNullish(rightTypeof)) return leftStr;
|
|
256
|
+
return null;
|
|
257
|
+
};
|
|
258
|
+
const rule$29 = {
|
|
259
|
+
create(context) {
|
|
260
|
+
return { BinaryExpression(node) {
|
|
261
|
+
if (!isReportable(node)) return;
|
|
262
|
+
if (Predicate.isObject(node) && isEqualityOperator(node["operator"])) {
|
|
263
|
+
const leftKind = getNullishSide(node["left"]);
|
|
264
|
+
const rightKind = getNullishSide(node["right"]);
|
|
265
|
+
const kind = leftKind ?? rightKind;
|
|
266
|
+
if (Predicate.isNotNullish(kind)) {
|
|
267
|
+
const predicateFn = kind === "null" ? "Predicate.isNull" : "Predicate.isUndefined";
|
|
268
|
+
context.report({
|
|
269
|
+
message: `Use ${predicateFn}() instead of direct ${kind} comparison`,
|
|
270
|
+
node
|
|
271
|
+
});
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const leftTypeof = getTypeofType(node["left"]);
|
|
275
|
+
const rightTypeof = getTypeofType(node["right"]);
|
|
276
|
+
const leftStr = getStringValue(node["left"]);
|
|
277
|
+
const rightStr = getStringValue(node["right"]);
|
|
278
|
+
const typeStr = resolveTypeStr(leftTypeof, rightTypeof, leftStr, rightStr);
|
|
279
|
+
if (Predicate.isNullish(typeStr)) return;
|
|
280
|
+
const predicateFn = typeofToPredicateMap[typeStr];
|
|
281
|
+
if (Predicate.isNullish(predicateFn)) return;
|
|
282
|
+
context.report({
|
|
283
|
+
message: `Use ${predicateFn}() instead of typeof comparison`,
|
|
284
|
+
node
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
} };
|
|
288
|
+
},
|
|
289
|
+
meta: { type: "problem" }
|
|
290
|
+
};
|
|
291
|
+
//#endregion
|
|
292
|
+
//#region src/rules/force-string-empty.ts
|
|
293
|
+
const isEmptyStringLiteral = (node) => Predicate.isObject(node) && node["type"] === "Literal" && Predicate.isString(node["value"]) && String.isEmpty(node["value"]);
|
|
294
|
+
const rule$28 = {
|
|
295
|
+
create(context) {
|
|
296
|
+
return { BinaryExpression(node) {
|
|
297
|
+
if (!isReportable(node)) return;
|
|
298
|
+
if (Predicate.isObject(node) && isEqualityOperator(node["operator"])) {
|
|
299
|
+
const leftEmpty = isEmptyStringLiteral(node["left"]);
|
|
300
|
+
const rightEmpty = isEmptyStringLiteral(node["right"]);
|
|
301
|
+
if (!leftEmpty && !rightEmpty) return;
|
|
302
|
+
const predicateFn = node["operator"] === "==" || node["operator"] === "===" ? "String.isEmpty" : "String.isNonEmpty";
|
|
303
|
+
context.report({
|
|
304
|
+
message: `Use ${predicateFn}() instead of direct empty string comparison`,
|
|
305
|
+
node
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
} };
|
|
309
|
+
},
|
|
310
|
+
meta: { type: "problem" }
|
|
311
|
+
};
|
|
312
|
+
//#endregion
|
|
313
|
+
//#region src/rules/no-effect-import-as.ts
|
|
314
|
+
const EFFECT_IMPORT_RE = /^@?effect(?:\/|$)/u;
|
|
315
|
+
const isEffectEcosystemImport = (sourceValue) => EFFECT_IMPORT_RE.test(sourceValue);
|
|
316
|
+
const checkSpecifierForAs = (spec, context, reportNode) => {
|
|
317
|
+
if (spec["type"] === "ImportNamespaceSpecifier") {
|
|
318
|
+
const localName = hasProperty(spec, "local") && Predicate.isObject(spec.local) && hasProperty(spec.local, "name") && Predicate.isString(spec.local.name) ? spec.local.name : "";
|
|
319
|
+
context.report({
|
|
320
|
+
message: `Avoid namespace import (\`import * as ${localName}\`) from the effect ecosystem. Use named imports directly.`,
|
|
321
|
+
node: reportNode
|
|
322
|
+
});
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
if (spec["type"] === "ImportSpecifier" && hasProperty(spec, "imported") && hasProperty(spec, "local") && Predicate.isObject(spec.imported) && Predicate.isObject(spec.local) && hasProperty(spec.imported, "name") && hasProperty(spec.local, "name") && Predicate.isString(spec.imported.name) && Predicate.isString(spec.local.name)) {
|
|
326
|
+
const { name: importedName } = spec.imported;
|
|
327
|
+
const { name: localName } = spec.local;
|
|
328
|
+
if (importedName !== localName) {
|
|
329
|
+
context.report({
|
|
330
|
+
message: `Avoid aliased import (\`${importedName} as ${localName}\`) from the effect ecosystem. Import \`${importedName}\` directly without aliasing.`,
|
|
331
|
+
node: reportNode
|
|
332
|
+
});
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return false;
|
|
337
|
+
};
|
|
338
|
+
const rule$27 = {
|
|
339
|
+
create(context) {
|
|
340
|
+
return { ImportDeclaration(node) {
|
|
341
|
+
const sourceValue = getImportSource(node);
|
|
342
|
+
if (Predicate.isNullish(sourceValue) || !isEffectEcosystemImport(sourceValue) || !isReportable(node)) return;
|
|
343
|
+
const specifiers = Predicate.isObject(node) && hasProperty(node, "specifiers") ? node["specifiers"] : null;
|
|
344
|
+
if (!Array.isArray(specifiers)) return;
|
|
345
|
+
for (const spec of specifiers) if (Predicate.isObject(spec) && checkSpecifierForAs(spec, context, node)) return;
|
|
346
|
+
} };
|
|
347
|
+
},
|
|
348
|
+
meta: { type: "problem" }
|
|
349
|
+
};
|
|
350
|
+
//#endregion
|
|
351
|
+
//#region src/rules/no-effect-runtime-run.ts
|
|
352
|
+
const RUNTIME_METHODS = /* @__PURE__ */ new Set([
|
|
353
|
+
"runCallback",
|
|
354
|
+
"runFork",
|
|
355
|
+
"runMain",
|
|
356
|
+
"runPromise",
|
|
357
|
+
"runPromiseExit",
|
|
358
|
+
"runSync"
|
|
359
|
+
]);
|
|
360
|
+
const getRuntimeMethod = (node) => {
|
|
361
|
+
if (Predicate.isObject(node) && node["type"] === "MemberExpression" && node["computed"] !== true && Predicate.isObject(node["property"]) && node["property"]["type"] === "Identifier" && Predicate.isString(node["property"]["name"]) && RUNTIME_METHODS.has(node["property"]["name"])) return node["property"]["name"];
|
|
362
|
+
return null;
|
|
363
|
+
};
|
|
364
|
+
const isRuntimeCallee = (node) => Predicate.isNotNullish(getRuntimeMethod(node));
|
|
365
|
+
const rule$26 = {
|
|
366
|
+
create(context) {
|
|
367
|
+
return { CallExpression(node) {
|
|
368
|
+
if (!isReportable(node) || !Predicate.isObject(node) || !hasProperty(node, "callee")) return;
|
|
369
|
+
if (!isRuntimeCallee(node["callee"])) return;
|
|
370
|
+
context.report({
|
|
371
|
+
message: "Run Effect runtimes only at workflow boundaries. Move runtime execution to the top-level entrypoint, or disable this rule with a reason for genuinely independent workflows (for example a client-side useEffect).",
|
|
372
|
+
node
|
|
373
|
+
});
|
|
374
|
+
} };
|
|
375
|
+
},
|
|
376
|
+
meta: { type: "problem" }
|
|
377
|
+
};
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region src/rules/no-effect-subpath-import.ts
|
|
380
|
+
const EFFECT_ECOSYSTEM_RE = /^@?effect(?:\/|$)/u;
|
|
381
|
+
const isAllowedEffectImport = (source) => {
|
|
382
|
+
if (source === "effect") return true;
|
|
383
|
+
if (/^@effect\/[^/]+$/u.test(source)) return true;
|
|
384
|
+
if (/^effect\/unstable\/[^/]+$/u.test(source)) return true;
|
|
385
|
+
return false;
|
|
386
|
+
};
|
|
387
|
+
const rule$25 = {
|
|
388
|
+
create(context) {
|
|
389
|
+
return { ImportDeclaration(node) {
|
|
390
|
+
if (!isReportable(node)) return;
|
|
391
|
+
if (!Predicate.isObject(node)) return;
|
|
392
|
+
if (!hasProperty(node, "source")) return;
|
|
393
|
+
const { source } = node;
|
|
394
|
+
if (!Predicate.isObject(source)) return;
|
|
395
|
+
if (!hasProperty(source, "value")) return;
|
|
396
|
+
if (!Predicate.isString(source.value)) return;
|
|
397
|
+
if (!EFFECT_ECOSYSTEM_RE.test(source.value)) return;
|
|
398
|
+
if (isAllowedEffectImport(source.value)) return;
|
|
399
|
+
context.report({
|
|
400
|
+
message: `Avoid subpath imports from the effect ecosystem. Import from the package root instead (e.g. \`@effect/platform-node\` not \`@effect/platform-node/NodeRuntime\`).`,
|
|
401
|
+
node
|
|
402
|
+
});
|
|
403
|
+
} };
|
|
404
|
+
},
|
|
405
|
+
meta: { type: "problem" }
|
|
406
|
+
};
|
|
407
|
+
//#endregion
|
|
408
|
+
//#region src/rules/no-error-cause-option.ts
|
|
409
|
+
const NO_ERROR_CAUSE_OPTION_MESSAGE = "Do not pass `{ cause }` to `new Error`. Use `{ error }` instead.";
|
|
410
|
+
const getIdentifierName$3 = (node) => Predicate.isObject(node) && node["type"] === "Identifier" && Predicate.isString(node["name"]) ? node["name"] : null;
|
|
411
|
+
const getPropertyKeyName = (node) => {
|
|
412
|
+
if (Predicate.isObject(node) && node["type"] === "Identifier" && Predicate.isString(node["name"])) return node["name"];
|
|
413
|
+
if (Predicate.isObject(node) && node["type"] === "Literal" && Predicate.isString(node["value"])) return node["value"];
|
|
414
|
+
return null;
|
|
415
|
+
};
|
|
416
|
+
const hasCauseProperty = (node) => Predicate.isObject(node) && node["type"] === "ObjectExpression" && hasProperty(node, "properties") && Array.isArray(node["properties"]) && node["properties"]["some"]((property) => Predicate.isObject(property) && hasProperty(property, "key") ? getPropertyKeyName(property.key) === "cause" : false);
|
|
417
|
+
const isNewErrorWithCauseOption = (node) => Predicate.isObject(node) && node["type"] === "NewExpression" && getIdentifierName$3(node["callee"]) === "Error" && hasProperty(node, "arguments") && Array.isArray(node["arguments"]) && node["arguments"]["some"](hasCauseProperty);
|
|
418
|
+
const rule$24 = {
|
|
419
|
+
create(context) {
|
|
420
|
+
return { NewExpression(node) {
|
|
421
|
+
if (!isReportable(node) || !isNewErrorWithCauseOption(node)) return;
|
|
422
|
+
context.report({
|
|
423
|
+
message: NO_ERROR_CAUSE_OPTION_MESSAGE,
|
|
424
|
+
node
|
|
425
|
+
});
|
|
426
|
+
} };
|
|
427
|
+
},
|
|
428
|
+
meta: { type: "problem" }
|
|
429
|
+
};
|
|
430
|
+
//#endregion
|
|
431
|
+
//#region src/rules/no-error-property-access.ts
|
|
432
|
+
const NO_ERROR_PROPERTY_ACCESS_MESSAGE = "Do not access properties on caught error values (`e`, `error`, `c`, `cause`, or `err`). Pass the value through as `error` unchanged.";
|
|
433
|
+
const ERROR_VALUE_NAMES = /* @__PURE__ */ new Set([
|
|
434
|
+
"c",
|
|
435
|
+
"cause",
|
|
436
|
+
"e",
|
|
437
|
+
"err",
|
|
438
|
+
"error"
|
|
439
|
+
]);
|
|
440
|
+
const getIdentifierName$2 = (node) => Predicate.isObject(node) && node["type"] === "Identifier" && Predicate.isString(node["name"]) ? node["name"] : null;
|
|
441
|
+
const isErrorPropertyAccess = (node) => Predicate.isObject(node) && node["type"] === "MemberExpression" && Predicate.isObject(node["object"]) && ERROR_VALUE_NAMES.has(getIdentifierName$2(node["object"]) ?? "");
|
|
442
|
+
const rule$23 = {
|
|
443
|
+
create(context) {
|
|
444
|
+
return { MemberExpression(node) {
|
|
445
|
+
if (!isReportable(node) || !isErrorPropertyAccess(node)) return;
|
|
446
|
+
context.report({
|
|
447
|
+
message: NO_ERROR_PROPERTY_ACCESS_MESSAGE,
|
|
448
|
+
node
|
|
449
|
+
});
|
|
450
|
+
} };
|
|
451
|
+
},
|
|
452
|
+
meta: { type: "problem" }
|
|
453
|
+
};
|
|
454
|
+
//#endregion
|
|
455
|
+
//#region src/rules/no-eslint-disable-comments.ts
|
|
456
|
+
const ESLINT_DISABLE_RE = /^\s*eslint-disable(?:-next-line|-line)?\b/gmu;
|
|
457
|
+
const rule$22 = {
|
|
458
|
+
create(context) {
|
|
459
|
+
return { "Program:exit"() {
|
|
460
|
+
const comments = context.sourceCode.getAllComments();
|
|
461
|
+
for (const comment of comments) {
|
|
462
|
+
const { value } = comment;
|
|
463
|
+
ESLINT_DISABLE_RE.lastIndex = 0;
|
|
464
|
+
if (ESLINT_DISABLE_RE.test(value)) {
|
|
465
|
+
const fixed = value.replaceAll("eslint-disable", "oxlint-disable");
|
|
466
|
+
context.report({
|
|
467
|
+
fix(fixer) {
|
|
468
|
+
const [start, end] = comment.range;
|
|
469
|
+
const prefix = comment.type === "Block" ? "/*" : "//";
|
|
470
|
+
const suffix = comment.type === "Block" ? "*/" : "";
|
|
471
|
+
return fixer.replaceTextRange([start, end], `${prefix}${fixed}${suffix}`);
|
|
472
|
+
},
|
|
473
|
+
message: "Use oxlint-disable instead of eslint-disable.",
|
|
474
|
+
node: comment
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
} };
|
|
479
|
+
},
|
|
480
|
+
meta: {
|
|
481
|
+
fixable: "code",
|
|
482
|
+
type: "problem"
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
//#endregion
|
|
486
|
+
//#region src/rules/no-fetch.ts
|
|
487
|
+
const FETCH_GLOBALS = /* @__PURE__ */ new Set([
|
|
488
|
+
"globalThis",
|
|
489
|
+
"self",
|
|
490
|
+
"window"
|
|
491
|
+
]);
|
|
492
|
+
const FETCH_GUIDANCE_MESSAGE = "Use Effect `HttpClient` (provide the `FetchHttpClient` layer) instead of the global `fetch`. Bypass only at external-library anti-corruption layers with a documented disable reason.";
|
|
493
|
+
const isFetchIdentifier = (node) => Predicate.isObject(node) && node["type"] === "Identifier" && hasProperty(node, "name") && node["name"] === "fetch";
|
|
494
|
+
const isFetchOnGlobal = (node) => {
|
|
495
|
+
if (!Predicate.isObject(node) || node["type"] !== "MemberExpression" || node["computed"] === true) return false;
|
|
496
|
+
if (!isFetchIdentifier(node["property"])) return false;
|
|
497
|
+
if (!Predicate.isObject(node["object"]) || node["object"]["type"] !== "Identifier") return false;
|
|
498
|
+
return hasProperty(node["object"], "name") && Predicate.isString(node["object"]["name"]) && FETCH_GLOBALS.has(node["object"]["name"]);
|
|
499
|
+
};
|
|
500
|
+
const rule$21 = {
|
|
501
|
+
create(context) {
|
|
502
|
+
return { CallExpression(node) {
|
|
503
|
+
if (!isReportable(node) || !Predicate.isObject(node) || !hasProperty(node, "callee")) return;
|
|
504
|
+
const { callee } = node;
|
|
505
|
+
if (isFetchIdentifier(callee) || isFetchOnGlobal(callee)) context.report({
|
|
506
|
+
message: FETCH_GUIDANCE_MESSAGE,
|
|
507
|
+
node
|
|
508
|
+
});
|
|
509
|
+
} };
|
|
510
|
+
},
|
|
511
|
+
meta: { type: "problem" }
|
|
512
|
+
};
|
|
513
|
+
//#endregion
|
|
514
|
+
//#region src/rules/no-instanceof-error.ts
|
|
515
|
+
const NO_INSTANCEOF_ERROR_MESSAGE = "Do not use `instanceof Error`. Preserve caught errors as `error` and handle narrowing only in the shared fp error helper with an explicit disable comment.";
|
|
516
|
+
const getIdentifierName$1 = (node) => Predicate.isObject(node) && node["type"] === "Identifier" && Predicate.isString(node["name"]) ? node["name"] : null;
|
|
517
|
+
const isInstanceofError = (node) => Predicate.isObject(node) && node["type"] === "BinaryExpression" && node["operator"] === "instanceof" && getIdentifierName$1(node["right"]) === "Error";
|
|
518
|
+
const rule$20 = {
|
|
519
|
+
create(context) {
|
|
520
|
+
return { BinaryExpression(node) {
|
|
521
|
+
if (!isReportable(node) || !isInstanceofError(node)) return;
|
|
522
|
+
context.report({
|
|
523
|
+
message: NO_INSTANCEOF_ERROR_MESSAGE,
|
|
524
|
+
node
|
|
525
|
+
});
|
|
526
|
+
} };
|
|
527
|
+
},
|
|
528
|
+
meta: { type: "problem" }
|
|
529
|
+
};
|
|
530
|
+
//#endregion
|
|
531
|
+
//#region src/rules/no-js-date.ts
|
|
532
|
+
const FORBIDDEN_DATE_STATIC_METHODS = /* @__PURE__ */ new Set([
|
|
533
|
+
"now",
|
|
534
|
+
"parse",
|
|
535
|
+
"UTC"
|
|
536
|
+
]);
|
|
537
|
+
const isDateIdentifier = (node) => Predicate.isObject(node) && node["type"] === "Identifier" && node["name"] === "Date";
|
|
538
|
+
const getForbiddenDateStaticMethod = (node) => {
|
|
539
|
+
if (Predicate.isObject(node) && node["type"] === "MemberExpression" && node["computed"] !== true && isDateIdentifier(node["object"]) && Predicate.isObject(node["property"]) && node["property"]["type"] === "Identifier" && Predicate.isString(node["property"]["name"]) && FORBIDDEN_DATE_STATIC_METHODS.has(node["property"]["name"])) return node["property"]["name"];
|
|
540
|
+
return null;
|
|
541
|
+
};
|
|
542
|
+
const rule$19 = {
|
|
543
|
+
create(context) {
|
|
544
|
+
return {
|
|
545
|
+
MemberExpression(node) {
|
|
546
|
+
if (!isReportable(node)) return;
|
|
547
|
+
const method = getForbiddenDateStaticMethod(node);
|
|
548
|
+
if (Predicate.isNotNullish(method)) context.report({
|
|
549
|
+
message: `Use Effect \`DateTime\` / \`Duration\` instead of \`Date.${method}()\`. Native \`Date\` is allowed only at external-API boundaries.`,
|
|
550
|
+
node
|
|
551
|
+
});
|
|
552
|
+
},
|
|
553
|
+
NewExpression(node) {
|
|
554
|
+
if (!isReportable(node)) return;
|
|
555
|
+
if (Predicate.isObject(node) && isDateIdentifier(node["callee"])) context.report({
|
|
556
|
+
message: "Use Effect `DateTime` / `Duration` instead of `new Date()`. Native `Date` is allowed only at external-API boundaries.",
|
|
557
|
+
node
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
},
|
|
562
|
+
meta: { type: "problem" }
|
|
563
|
+
};
|
|
564
|
+
//#endregion
|
|
565
|
+
//#region src/rules/no-jsx-script-tag.ts
|
|
566
|
+
const isScriptJsxElement = (node) => Predicate.isObject(node) && node["type"] === "JSXOpeningElement" && Predicate.isObject(node["name"]) && node["name"].type === "JSXIdentifier" && node["name"].name === "script";
|
|
567
|
+
const rule$18 = {
|
|
568
|
+
create(context) {
|
|
569
|
+
return { JSXOpeningElement(node) {
|
|
570
|
+
if (!isReportable(node)) return;
|
|
571
|
+
if (isScriptJsxElement(node)) context.report({
|
|
572
|
+
message: "Do not use <script> tags in JSX. Write client-side logic in React or Remix v3 components instead. If unavoidable (e.g. a third-party inline script), disable this rule with an oxlint-disable comment that includes the reason.",
|
|
573
|
+
node
|
|
574
|
+
});
|
|
575
|
+
} };
|
|
576
|
+
},
|
|
577
|
+
meta: { type: "problem" }
|
|
578
|
+
};
|
|
579
|
+
//#endregion
|
|
580
|
+
//#region src/rules/no-let.ts
|
|
581
|
+
const isLetDeclaration = (node) => Predicate.isObject(node) && node["kind"] === "let";
|
|
582
|
+
const rule$17 = {
|
|
583
|
+
create(context) {
|
|
584
|
+
return { VariableDeclaration(node) {
|
|
585
|
+
if (!isReportable(node)) return;
|
|
586
|
+
if (isLetDeclaration(node)) context.report({
|
|
587
|
+
message: "Use const with shadowing instead of let. Reassignment via let is prohibited.",
|
|
588
|
+
node
|
|
589
|
+
});
|
|
590
|
+
} };
|
|
591
|
+
},
|
|
592
|
+
meta: { type: "problem" }
|
|
593
|
+
};
|
|
594
|
+
//#endregion
|
|
595
|
+
//#region src/rules/no-node-imports.ts
|
|
596
|
+
const NODE_PROTOCOL = "node:";
|
|
597
|
+
const NODE_IMPORT_GUIDANCE_MESSAGE = "Avoid Node.js built-in imports. Prefer Effect Platform APIs from `effect` / `@effect/platform-node`; add a rule whitelist entry only for documented exceptions.";
|
|
598
|
+
const getNodeBuiltinSpecifier = (source) => {
|
|
599
|
+
if (!source.startsWith(NODE_PROTOCOL)) return null;
|
|
600
|
+
return source.slice(5);
|
|
601
|
+
};
|
|
602
|
+
const getStringArrayProperty = (value, key) => {
|
|
603
|
+
if (!Predicate.isObject(value) || !hasProperty(value, key) || !Array.isArray(value[key])) return [];
|
|
604
|
+
return value[key].filter(Predicate.isString);
|
|
605
|
+
};
|
|
606
|
+
const getContextOptions = (context) => {
|
|
607
|
+
if (!Predicate.isObject(context) || !hasProperty(context, "options")) return [];
|
|
608
|
+
const { options } = context;
|
|
609
|
+
return Array.isArray(options) ? options : [];
|
|
610
|
+
};
|
|
611
|
+
const getAllowedNodeBuiltins = (context) => {
|
|
612
|
+
const first = getContextOptions(context).at(0);
|
|
613
|
+
if (Array.isArray(first)) return new Set(first.filter(Predicate.isString));
|
|
614
|
+
return /* @__PURE__ */ new Set([...getStringArrayProperty(first, "allow"), ...getStringArrayProperty(first, "allowed")]);
|
|
615
|
+
};
|
|
616
|
+
const reportNodeImport = (node, context) => {
|
|
617
|
+
const source = getImportSource(node);
|
|
618
|
+
if (Predicate.isNullish(source) || !isReportable(node)) return;
|
|
619
|
+
const builtin = getNodeBuiltinSpecifier(source);
|
|
620
|
+
if (Predicate.isNullish(builtin) || getAllowedNodeBuiltins(context).has(builtin)) return;
|
|
621
|
+
context.report({
|
|
622
|
+
message: NODE_IMPORT_GUIDANCE_MESSAGE,
|
|
623
|
+
node
|
|
624
|
+
});
|
|
625
|
+
};
|
|
626
|
+
const rule$16 = {
|
|
627
|
+
create(context) {
|
|
628
|
+
return {
|
|
629
|
+
ExportAllDeclaration(node) {
|
|
630
|
+
reportNodeImport(node, context);
|
|
631
|
+
},
|
|
632
|
+
ExportNamedDeclaration(node) {
|
|
633
|
+
reportNodeImport(node, context);
|
|
634
|
+
},
|
|
635
|
+
ImportDeclaration(node) {
|
|
636
|
+
reportNodeImport(node, context);
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
},
|
|
640
|
+
meta: {
|
|
641
|
+
schema: [{ anyOf: [{
|
|
642
|
+
items: { type: "string" },
|
|
643
|
+
type: "array"
|
|
644
|
+
}, {
|
|
645
|
+
additionalProperties: false,
|
|
646
|
+
properties: {
|
|
647
|
+
allow: {
|
|
648
|
+
items: { type: "string" },
|
|
649
|
+
type: "array"
|
|
650
|
+
},
|
|
651
|
+
allowed: {
|
|
652
|
+
items: { type: "string" },
|
|
653
|
+
type: "array"
|
|
654
|
+
}
|
|
655
|
+
},
|
|
656
|
+
type: "object"
|
|
657
|
+
}] }],
|
|
658
|
+
type: "problem"
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
//#endregion
|
|
662
|
+
//#region src/rules/no-option-tag-comparison.ts
|
|
663
|
+
const tagToPredicateMap = {
|
|
664
|
+
None: "Option.isNone",
|
|
665
|
+
Some: "Option.isSome"
|
|
666
|
+
};
|
|
667
|
+
const getOptionTag = (node) => {
|
|
668
|
+
if (Predicate.isObject(node) && node["type"] === "Literal" && Predicate.isString(node["value"])) {
|
|
669
|
+
const { value } = node;
|
|
670
|
+
if (value in tagToPredicateMap) return value;
|
|
671
|
+
}
|
|
672
|
+
return null;
|
|
673
|
+
};
|
|
674
|
+
const isTagPropertyAccess = (node) => Predicate.isObject(node) && node["type"] === "MemberExpression" && node["computed"] !== true && Predicate.isObject(node["property"]) && node["property"]["type"] === "Identifier" && node["property"]["name"] === "_tag";
|
|
675
|
+
const isTypeofTag = (node) => Predicate.isObject(node) && node["type"] === "UnaryExpression" && node["operator"] === "typeof" && isTagPropertyAccess(node["argument"]);
|
|
676
|
+
const rule$15 = {
|
|
677
|
+
create(context) {
|
|
678
|
+
return { BinaryExpression(node) {
|
|
679
|
+
if (!isReportable(node)) return;
|
|
680
|
+
if (Predicate.isObject(node) && isEqualityOperator(node["operator"])) {
|
|
681
|
+
const leftTag = getOptionTag(node["left"]);
|
|
682
|
+
const rightTag = getOptionTag(node["right"]);
|
|
683
|
+
const tag = leftTag ?? rightTag;
|
|
684
|
+
if (Predicate.isNullish(tag)) return;
|
|
685
|
+
const otherSide = Predicate.isNotNullish(leftTag) ? node["right"] : node["left"];
|
|
686
|
+
if (isTagPropertyAccess(otherSide) || isTypeofTag(otherSide)) {
|
|
687
|
+
const predicateFn = tagToPredicateMap[tag];
|
|
688
|
+
const isEquality = node["operator"] === "==" || node["operator"] === "===";
|
|
689
|
+
context.report({
|
|
690
|
+
fix(fixer) {
|
|
691
|
+
const rawTarget = hasProperty(otherSide, "object") ? otherSide.object : otherSide;
|
|
692
|
+
if (!isReportable(rawTarget)) return null;
|
|
693
|
+
const sourceText = context.sourceCode.getText(rawTarget);
|
|
694
|
+
const prefix = isEquality ? "" : "!";
|
|
695
|
+
return fixer.replaceText(node, `${prefix}${predicateFn}(${sourceText})`);
|
|
696
|
+
},
|
|
697
|
+
message: `Use ${predicateFn}() instead of _tag === '${tag}'`,
|
|
698
|
+
node
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
} };
|
|
703
|
+
},
|
|
704
|
+
meta: {
|
|
705
|
+
fixable: "code",
|
|
706
|
+
type: "problem"
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
//#endregion
|
|
710
|
+
//#region src/rules/no-raw-hono-create-middleware.ts
|
|
711
|
+
const HONO_FACTORY_SOURCE = "hono/factory";
|
|
712
|
+
const CREATE_MIDDLEWARE_NAME = "createMiddleware";
|
|
713
|
+
const MESSAGE = "Do not use raw `createMiddleware` from `hono/factory` in application code. Use the shared `factory.createMiddleware` instance instead. Shared library code may disable this rule with a reason.";
|
|
714
|
+
const getIdentifierName = (node) => Predicate.isObject(node) && hasProperty(node, "name") && Predicate.isString(node["name"]) ? node["name"] : null;
|
|
715
|
+
const isCreateMiddlewareImportSpecifier = (node) => Predicate.isObject(node) && node["type"] === "ImportSpecifier" && hasProperty(node, "imported") && getIdentifierName(node["imported"]) === CREATE_MIDDLEWARE_NAME;
|
|
716
|
+
const isNamespaceCreateMiddlewareCall = (node, namespaceNames) => {
|
|
717
|
+
if (!Predicate.isObject(node) || node["type"] !== "CallExpression" || !hasProperty(node, "callee")) return false;
|
|
718
|
+
const { callee } = node;
|
|
719
|
+
if (!Predicate.isObject(callee) || callee["type"] !== "MemberExpression" || callee["computed"] === true) return false;
|
|
720
|
+
if (getIdentifierName(callee["property"]) !== CREATE_MIDDLEWARE_NAME) return false;
|
|
721
|
+
const objectName = getIdentifierName(callee["object"]);
|
|
722
|
+
return !Predicate.isNullish(objectName) && namespaceNames.has(objectName);
|
|
723
|
+
};
|
|
724
|
+
const rule$14 = {
|
|
725
|
+
create(context) {
|
|
726
|
+
const honoFactoryNamespaceNames = /* @__PURE__ */ new Set();
|
|
727
|
+
return {
|
|
728
|
+
CallExpression(node) {
|
|
729
|
+
if (!isReportable(node) || !isNamespaceCreateMiddlewareCall(node, honoFactoryNamespaceNames)) return;
|
|
730
|
+
context.report({
|
|
731
|
+
message: MESSAGE,
|
|
732
|
+
node
|
|
733
|
+
});
|
|
734
|
+
},
|
|
735
|
+
ImportDeclaration(node) {
|
|
736
|
+
if (getImportSource(node) !== HONO_FACTORY_SOURCE || !isReportable(node)) return;
|
|
737
|
+
const specifiers = Predicate.isObject(node) && hasProperty(node, "specifiers") ? node["specifiers"] : null;
|
|
738
|
+
if (!Array.isArray(specifiers)) return;
|
|
739
|
+
for (const specifier of specifiers) {
|
|
740
|
+
if (!Predicate.isObject(specifier)) continue;
|
|
741
|
+
if (isCreateMiddlewareImportSpecifier(specifier)) {
|
|
742
|
+
context.report({
|
|
743
|
+
message: MESSAGE,
|
|
744
|
+
node
|
|
745
|
+
});
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
if (specifier["type"] === "ImportNamespaceSpecifier" && hasProperty(specifier, "local")) {
|
|
749
|
+
const localName = getIdentifierName(specifier.local);
|
|
750
|
+
if (!Predicate.isNullish(localName)) honoFactoryNamespaceNames.add(localName);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
},
|
|
756
|
+
meta: { type: "problem" }
|
|
757
|
+
};
|
|
758
|
+
//#endregion
|
|
759
|
+
//#region src/rules/no-redundant-alias.ts
|
|
760
|
+
const REDUNDANT_TYPE_ALIAS_MESSAGE = "Avoid type aliases that merely rename another type (e.g. `type SentEmail = SendParams`). Inline the original, or keep the alias only inside an anti-corruption layer with a documented disable reason.";
|
|
761
|
+
const REDUNDANT_VALUE_ALIAS_MESSAGE = "Avoid exported bindings that merely rename another value (e.g. `export const sentEmail = sendParams`). Re-export the original directly, or keep the alias only inside an anti-corruption layer with a documented disable reason.";
|
|
762
|
+
const hasTypeArguments = (node) => Predicate.isObject(node) && hasProperty(node, "typeArguments") && Predicate.isNotNullish(node["typeArguments"]);
|
|
763
|
+
const hasGenericTypeParameters = (node) => Predicate.isObject(node) && hasProperty(node, "typeParameters") && Predicate.isNotNullish(node["typeParameters"]);
|
|
764
|
+
const isPlainTypeReference = (node) => Predicate.isObject(node) && node["type"] === "TSTypeReference" && !hasTypeArguments(node);
|
|
765
|
+
const isRedundantTypeAlias = (node) => {
|
|
766
|
+
if (!Predicate.isObject(node) || node["type"] !== "TSTypeAliasDeclaration") return false;
|
|
767
|
+
if (hasGenericTypeParameters(node)) return false;
|
|
768
|
+
return hasProperty(node, "typeAnnotation") && isPlainTypeReference(node["typeAnnotation"]);
|
|
769
|
+
};
|
|
770
|
+
const isIdentifierInitDeclarator = (declarator) => Predicate.isObject(declarator) && hasProperty(declarator, "id") && Predicate.isObject(declarator.id) && declarator.id["type"] === "Identifier" && hasProperty(declarator, "init") && Predicate.isObject(declarator.init) && declarator.init["type"] === "Identifier";
|
|
771
|
+
const rule$13 = {
|
|
772
|
+
create(context) {
|
|
773
|
+
return {
|
|
774
|
+
ExportNamedDeclaration(node) {
|
|
775
|
+
if (!isReportable(node) || !Predicate.isObject(node) || !hasProperty(node, "declaration")) return;
|
|
776
|
+
const { declaration } = node;
|
|
777
|
+
if (!Predicate.isObject(declaration) || declaration["type"] !== "VariableDeclaration" || !hasProperty(declaration, "declarations") || !Array.isArray(declaration.declarations)) return;
|
|
778
|
+
const declarationItems = declaration.declarations;
|
|
779
|
+
for (const declarator of declarationItems) if (isReportable(declarator) && isIdentifierInitDeclarator(declarator)) context.report({
|
|
780
|
+
message: REDUNDANT_VALUE_ALIAS_MESSAGE,
|
|
781
|
+
node: declarator
|
|
782
|
+
});
|
|
783
|
+
},
|
|
784
|
+
TSTypeAliasDeclaration(node) {
|
|
785
|
+
if (!isReportable(node)) return;
|
|
786
|
+
if (isRedundantTypeAlias(node)) context.report({
|
|
787
|
+
message: REDUNDANT_TYPE_ALIAS_MESSAGE,
|
|
788
|
+
node
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
},
|
|
793
|
+
meta: { type: "problem" }
|
|
794
|
+
};
|
|
795
|
+
//#endregion
|
|
796
|
+
//#region src/rules/no-string-style.ts
|
|
797
|
+
const hasStringStyleValue = (node) => Predicate.isObject(node) && node["type"] === "JSXAttribute" && Predicate.isObject(node["name"]) && node["name"].name === "style" && Predicate.isObject(node["value"]) && node["value"].type === "Literal" && Predicate.isString(node["value"].value);
|
|
798
|
+
const rule$12 = {
|
|
799
|
+
create(context) {
|
|
800
|
+
return { JSXAttribute(node) {
|
|
801
|
+
if (!isReportable(node)) return;
|
|
802
|
+
if (hasStringStyleValue(node)) context.report({
|
|
803
|
+
message: "Use object-style `style={{ ... }}` instead of string `style=\"...\"`.",
|
|
804
|
+
node
|
|
805
|
+
});
|
|
806
|
+
} };
|
|
807
|
+
},
|
|
808
|
+
meta: { type: "problem" }
|
|
809
|
+
};
|
|
810
|
+
//#endregion
|
|
811
|
+
//#region src/rules/no-sync-decode.ts
|
|
812
|
+
const BANNED_DECODE_METHODS = {
|
|
813
|
+
decodePromise: "decodeEffect",
|
|
814
|
+
decodeSync: "decodeEffect",
|
|
815
|
+
decodeUnknownPromise: "decodeUnknownEffect",
|
|
816
|
+
decodeUnknownSync: "decodeUnknownEffect"
|
|
817
|
+
};
|
|
818
|
+
const isSchemaBannedDecodeCall = (node) => {
|
|
819
|
+
for (const method of Object.keys(BANNED_DECODE_METHODS)) if (isSchemaMethodCall(node, method)) return method;
|
|
820
|
+
return null;
|
|
821
|
+
};
|
|
822
|
+
const rule$11 = {
|
|
823
|
+
create(context) {
|
|
824
|
+
return { CallExpression(node) {
|
|
825
|
+
if (!isReportable(node)) return;
|
|
826
|
+
const method = isSchemaBannedDecodeCall(node);
|
|
827
|
+
if (Predicate.isNotNullish(method)) {
|
|
828
|
+
const recommended = BANNED_DECODE_METHODS[method];
|
|
829
|
+
context.report({
|
|
830
|
+
message: `Use Schema.${recommended} or Schema.decodeExit instead of Schema.${method}.`,
|
|
831
|
+
node
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
} };
|
|
835
|
+
},
|
|
836
|
+
meta: { type: "problem" }
|
|
837
|
+
};
|
|
838
|
+
//#endregion
|
|
839
|
+
//#region src/rules/no-type-predicate.ts
|
|
840
|
+
const NO_TYPE_PREDICATE_MESSAGE = "Do not use TypeScript type predicate annotations (`value is Type`). Model the value with Effect Schema and use `Schema.is(...)` instead.";
|
|
841
|
+
const isTypePredicateAnnotation = (node) => Predicate.isObject(node) && node["type"] === "TSTypePredicate";
|
|
842
|
+
const rule$10 = {
|
|
843
|
+
create(context) {
|
|
844
|
+
return { TSTypePredicate(node) {
|
|
845
|
+
if (!isReportable(node) || !isTypePredicateAnnotation(node)) return;
|
|
846
|
+
context.report({
|
|
847
|
+
message: NO_TYPE_PREDICATE_MESSAGE,
|
|
848
|
+
node
|
|
849
|
+
});
|
|
850
|
+
} };
|
|
851
|
+
},
|
|
852
|
+
meta: { type: "problem" }
|
|
853
|
+
};
|
|
854
|
+
//#endregion
|
|
855
|
+
//#region src/rules/prefer-is-nullish.ts
|
|
856
|
+
const NULLISH_PREDICATE_METHODS = /* @__PURE__ */ new Set([
|
|
857
|
+
"isNull",
|
|
858
|
+
"isNotNull",
|
|
859
|
+
"isUndefined",
|
|
860
|
+
"isNotUndefined"
|
|
861
|
+
]);
|
|
862
|
+
const getNullishPredicateMember = (node) => {
|
|
863
|
+
if (!Predicate.isObject(node) || !Predicate.isObject(node["object"]) || node["object"]["type"] !== "Identifier" || node["object"]["name"] !== "Predicate" || !Predicate.isObject(node["property"]) || node["property"]["type"] !== "Identifier" || !Predicate.isString(node["property"]["name"])) return null;
|
|
864
|
+
const { name } = node["property"];
|
|
865
|
+
return NULLISH_PREDICATE_METHODS.has(name) ? name : null;
|
|
866
|
+
};
|
|
867
|
+
const rule$9 = {
|
|
868
|
+
create(context) {
|
|
869
|
+
return { MemberExpression(node) {
|
|
870
|
+
if (!isReportable(node)) return;
|
|
871
|
+
const method = getNullishPredicateMember(node);
|
|
872
|
+
if (Predicate.isNullish(method)) return;
|
|
873
|
+
const recommended = method.startsWith("isNot") ? "isNotNullish" : "isNullish";
|
|
874
|
+
context.report({
|
|
875
|
+
message: `Use Predicate.${recommended} to treat null and undefined uniformly. Only when distinguishing them is required, disable this rule with an oxlint-disable comment that includes the reason.`,
|
|
876
|
+
node
|
|
877
|
+
});
|
|
878
|
+
} };
|
|
879
|
+
},
|
|
880
|
+
meta: { type: "suggestion" }
|
|
881
|
+
};
|
|
882
|
+
//#endregion
|
|
883
|
+
//#region src/rules/prefer-non-unknown-decode.ts
|
|
884
|
+
const UNKNOWN_DECODE_METHODS = {
|
|
885
|
+
decodeUnknownEffect: "decodeEffect",
|
|
886
|
+
decodeUnknownExit: "decodeExit"
|
|
887
|
+
};
|
|
888
|
+
const isSchemaUnknownDecodeCall = (node) => {
|
|
889
|
+
for (const method of Object.keys(UNKNOWN_DECODE_METHODS)) if (isSchemaMethodCall(node, method)) return method;
|
|
890
|
+
return null;
|
|
891
|
+
};
|
|
892
|
+
const rule$8 = {
|
|
893
|
+
create(context) {
|
|
894
|
+
return { CallExpression(node) {
|
|
895
|
+
if (!isReportable(node)) return;
|
|
896
|
+
const method = isSchemaUnknownDecodeCall(node);
|
|
897
|
+
if (Predicate.isNotNullish(method)) {
|
|
898
|
+
const recommended = UNKNOWN_DECODE_METHODS[method];
|
|
899
|
+
context.report({
|
|
900
|
+
message: `Prefer Schema.${recommended} over Schema.${method}. Use unknown variants only when input type is truly unknown. Add an oxlint-disable comment if the unknown variant is required.`,
|
|
901
|
+
node
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
} };
|
|
905
|
+
},
|
|
906
|
+
meta: { type: "suggestion" }
|
|
907
|
+
};
|
|
908
|
+
//#endregion
|
|
909
|
+
//#region src/rules/require-disable-reason.ts
|
|
910
|
+
const OXLINT_DISABLE_DIRECTIVE_RE = /\boxlint-disable\b/u;
|
|
911
|
+
const DISABLE_REASON_RE = /\s--\s+\S/u;
|
|
912
|
+
const isOxlintDisableDirective = (value) => OXLINT_DISABLE_DIRECTIVE_RE.test(value);
|
|
913
|
+
const hasDisableReason = (value) => DISABLE_REASON_RE.test(value);
|
|
914
|
+
const rule$7 = {
|
|
915
|
+
create(context) {
|
|
916
|
+
return { "Program:exit"() {
|
|
917
|
+
const comments = context.sourceCode.getAllComments();
|
|
918
|
+
for (const comment of comments) {
|
|
919
|
+
const { value } = comment;
|
|
920
|
+
if (!isOxlintDisableDirective(value)) continue;
|
|
921
|
+
if (hasDisableReason(value)) continue;
|
|
922
|
+
context.report({
|
|
923
|
+
message: "oxlint-disable comments must include a reason in the form `<disable directive> -- <reason>`.",
|
|
924
|
+
node: comment
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
} };
|
|
928
|
+
},
|
|
929
|
+
meta: { type: "problem" }
|
|
930
|
+
};
|
|
931
|
+
//#endregion
|
|
932
|
+
//#region src/rules/require-import-extension.ts
|
|
933
|
+
const rule$6 = {
|
|
934
|
+
create: (context) => createImportExtensionListeners(context, (value) => {
|
|
935
|
+
const match = matchImportPath(value);
|
|
936
|
+
return match !== null && (match.extension === "" || match.directory) ? value : null;
|
|
937
|
+
}, "Use an explicit extension in relative and subpath imports"),
|
|
938
|
+
meta: {
|
|
939
|
+
schema: [],
|
|
940
|
+
type: "problem"
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
//#endregion
|
|
944
|
+
//#region src/rules/require-top-level-decoder.ts
|
|
945
|
+
const TOP_LEVEL_DECODER_MESSAGE = "Hoist schema decoder construction to module top level and call the cached function inside this scope. Building the decoder per call re-runs the schema compilation on every invocation.";
|
|
946
|
+
const matchMemberCallByPrefix = (node, namespace, prefix) => {
|
|
947
|
+
if (!Predicate.isObject(node) || node["type"] !== "CallExpression" || !Predicate.isObject(node["callee"]) || node["callee"]["type"] !== "MemberExpression" || Predicate.isTruthy(node["callee"]["computed"])) return false;
|
|
948
|
+
const { object, property } = node["callee"];
|
|
949
|
+
if (!Predicate.isObject(object) || object["type"] !== "Identifier" || !hasProperty(object, "name") || object.name !== namespace) return false;
|
|
950
|
+
if (!Predicate.isObject(property) || property["type"] !== "Identifier" || !hasProperty(property, "name") || !Predicate.isString(property.name)) return false;
|
|
951
|
+
return property.name.startsWith(prefix);
|
|
952
|
+
};
|
|
953
|
+
const isHoistableDecoderCall = (node) => matchMemberCallByPrefix(node, "Schema", "decode") || matchMemberCallByPrefix(node, "HttpClientResponse", "schema");
|
|
954
|
+
const rule$5 = {
|
|
955
|
+
create(context) {
|
|
956
|
+
const functionStack = [];
|
|
957
|
+
const enterFunction = () => {
|
|
958
|
+
functionStack.push(true);
|
|
959
|
+
};
|
|
960
|
+
const exitFunction = () => {
|
|
961
|
+
functionStack.pop();
|
|
962
|
+
};
|
|
963
|
+
return {
|
|
964
|
+
ArrowFunctionExpression: enterFunction,
|
|
965
|
+
"ArrowFunctionExpression:exit": exitFunction,
|
|
966
|
+
CallExpression(node) {
|
|
967
|
+
if (Predicate.isNullish(functionStack[0]) || !isReportable(node)) return;
|
|
968
|
+
if (isHoistableDecoderCall(node)) context.report({
|
|
969
|
+
message: TOP_LEVEL_DECODER_MESSAGE,
|
|
970
|
+
node
|
|
971
|
+
});
|
|
972
|
+
},
|
|
973
|
+
FunctionDeclaration: enterFunction,
|
|
974
|
+
"FunctionDeclaration:exit": exitFunction,
|
|
975
|
+
FunctionExpression: enterFunction,
|
|
976
|
+
"FunctionExpression:exit": exitFunction
|
|
977
|
+
};
|
|
978
|
+
},
|
|
979
|
+
meta: { type: "problem" }
|
|
980
|
+
};
|
|
981
|
+
//#endregion
|
|
982
|
+
//#region src/upstream/effect/no-bigint-literals.ts
|
|
983
|
+
const rule$4 = {
|
|
984
|
+
meta: {
|
|
985
|
+
type: "problem",
|
|
986
|
+
docs: { description: "Disallow bigint literals" },
|
|
987
|
+
fixable: "code"
|
|
988
|
+
},
|
|
989
|
+
create(context) {
|
|
990
|
+
return { Literal(node) {
|
|
991
|
+
if (typeof node.value === "bigint") {
|
|
992
|
+
const fixedSource = `BigInt("${node.value}")`;
|
|
993
|
+
context.report({
|
|
994
|
+
node,
|
|
995
|
+
message: "BigInt literals are not allowed",
|
|
996
|
+
fix: (fixer) => fixer.replaceText(node, fixedSource)
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
} };
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
//#endregion
|
|
1003
|
+
//#region src/upstream/effect/no-import-from-barrel-package.ts
|
|
1004
|
+
const extensions = [
|
|
1005
|
+
".ts",
|
|
1006
|
+
".tsx",
|
|
1007
|
+
".mts",
|
|
1008
|
+
".cts",
|
|
1009
|
+
".js",
|
|
1010
|
+
".jsx",
|
|
1011
|
+
".mjs",
|
|
1012
|
+
".cjs"
|
|
1013
|
+
];
|
|
1014
|
+
function getModuleName(specifier) {
|
|
1015
|
+
return specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value;
|
|
1016
|
+
}
|
|
1017
|
+
function isRelativeImport$1(source) {
|
|
1018
|
+
return source.startsWith("./") || source.startsWith("../");
|
|
1019
|
+
}
|
|
1020
|
+
function hasIndexFile(dirPath) {
|
|
1021
|
+
for (const ext of extensions) if (fs.existsSync(path.join(dirPath, `index${ext}`))) return true;
|
|
1022
|
+
return false;
|
|
1023
|
+
}
|
|
1024
|
+
function isIndexImport(importPath) {
|
|
1025
|
+
const basename = path.basename(importPath);
|
|
1026
|
+
return basename === "index" || /^index\.(ts|tsx|js|jsx|mts|cts|mjs|cjs)$/.test(basename);
|
|
1027
|
+
}
|
|
1028
|
+
function resolvesToBarrel(importSource, currentFile) {
|
|
1029
|
+
const dir = path.dirname(currentFile);
|
|
1030
|
+
const resolved = path.resolve(dir, importSource);
|
|
1031
|
+
if (isIndexImport(importSource)) return true;
|
|
1032
|
+
if (hasIndexFile(resolved)) return true;
|
|
1033
|
+
return false;
|
|
1034
|
+
}
|
|
1035
|
+
function createBarrelMatcher(options) {
|
|
1036
|
+
const patterns = (options.checkPatterns ?? []).map((p) => new RegExp(p));
|
|
1037
|
+
const checkRelative = options.checkRelativeIndexImports !== false;
|
|
1038
|
+
return (source, currentFile) => {
|
|
1039
|
+
if (isRelativeImport$1(source)) return checkRelative && resolvesToBarrel(source, currentFile);
|
|
1040
|
+
for (const pattern of patterns) if (pattern.test(source)) return true;
|
|
1041
|
+
return false;
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
const rule$3 = {
|
|
1045
|
+
meta: {
|
|
1046
|
+
type: "suggestion",
|
|
1047
|
+
docs: { description: "Disallow importing from barrel files (index.ts), encourage importing specific modules instead" },
|
|
1048
|
+
schema: [{
|
|
1049
|
+
type: "object",
|
|
1050
|
+
properties: {
|
|
1051
|
+
checkPatterns: {
|
|
1052
|
+
type: "array",
|
|
1053
|
+
items: { type: "string" },
|
|
1054
|
+
description: "Regex patterns to match barrel imports"
|
|
1055
|
+
},
|
|
1056
|
+
checkRelativeIndexImports: {
|
|
1057
|
+
type: "boolean",
|
|
1058
|
+
description: "Whether to check relative imports that resolve to index files"
|
|
1059
|
+
}
|
|
1060
|
+
},
|
|
1061
|
+
additionalProperties: false
|
|
1062
|
+
}]
|
|
1063
|
+
},
|
|
1064
|
+
create(context) {
|
|
1065
|
+
const currentFile = context.filename;
|
|
1066
|
+
const isBarrelImport = createBarrelMatcher(context.options[0] ?? {});
|
|
1067
|
+
return { ImportDeclaration(node) {
|
|
1068
|
+
if (node.importKind === "type") return;
|
|
1069
|
+
const importSource = node.source.value;
|
|
1070
|
+
if (!isBarrelImport(importSource, currentFile)) return;
|
|
1071
|
+
const namespaceSpecifiers = [];
|
|
1072
|
+
const namedValueSpecifiers = [];
|
|
1073
|
+
for (const specifier of node.specifiers) if (specifier.type === "ImportNamespaceSpecifier") namespaceSpecifiers.push(specifier);
|
|
1074
|
+
else if (specifier.type === "ImportSpecifier") {
|
|
1075
|
+
if (specifier.importKind !== "type") namedValueSpecifiers.push(specifier);
|
|
1076
|
+
}
|
|
1077
|
+
for (const specifier of namespaceSpecifiers) context.report({
|
|
1078
|
+
node: specifier,
|
|
1079
|
+
message: `Do not use namespace import from barrel file "${importSource}", import from specific modules instead`
|
|
1080
|
+
});
|
|
1081
|
+
for (const specifier of namedValueSpecifiers) {
|
|
1082
|
+
const moduleName = getModuleName(specifier);
|
|
1083
|
+
const localName = specifier.local.name;
|
|
1084
|
+
const message = isRelativeImport$1(importSource) ? `Do not import "${moduleName}" from barrel file "${importSource}", import from specific module instead` : `Use import * as ${localName} from "${importSource}/${moduleName}" instead`;
|
|
1085
|
+
context.report({
|
|
1086
|
+
node: specifier,
|
|
1087
|
+
message
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
} };
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
//#endregion
|
|
1094
|
+
//#region src/upstream/effect/no-js-extension-imports.ts
|
|
1095
|
+
const jsExtensions = [
|
|
1096
|
+
".js",
|
|
1097
|
+
".jsx",
|
|
1098
|
+
".mjs",
|
|
1099
|
+
".cjs"
|
|
1100
|
+
];
|
|
1101
|
+
const extensionMap = {
|
|
1102
|
+
".js": ".ts",
|
|
1103
|
+
".jsx": ".tsx",
|
|
1104
|
+
".mjs": ".mts",
|
|
1105
|
+
".cjs": ".cts"
|
|
1106
|
+
};
|
|
1107
|
+
function isRelativeImport(source) {
|
|
1108
|
+
return source.startsWith("./") || source.startsWith("../");
|
|
1109
|
+
}
|
|
1110
|
+
function getJsExtension(source) {
|
|
1111
|
+
for (const ext of jsExtensions) if (source.endsWith(ext)) return ext;
|
|
1112
|
+
}
|
|
1113
|
+
const rule$2 = {
|
|
1114
|
+
meta: {
|
|
1115
|
+
type: "problem",
|
|
1116
|
+
docs: { description: "Disallow .js, .jsx, .mjs and .cjs extensions in relative imports, use .ts, .tsx, .mts or .cts instead" },
|
|
1117
|
+
fixable: "code"
|
|
1118
|
+
},
|
|
1119
|
+
create(context) {
|
|
1120
|
+
function checkSource(source) {
|
|
1121
|
+
const value = source.value;
|
|
1122
|
+
if (!isRelativeImport(value)) return;
|
|
1123
|
+
const ext = getJsExtension(value);
|
|
1124
|
+
if (!ext) return;
|
|
1125
|
+
const tsExt = extensionMap[ext];
|
|
1126
|
+
const fixedSource = value.slice(0, -ext.length) + tsExt;
|
|
1127
|
+
context.report({
|
|
1128
|
+
node: source,
|
|
1129
|
+
message: `Use "${tsExt}" extension instead of "${ext}" for relative imports`,
|
|
1130
|
+
fix: (fixer) => fixer.replaceTextRange(source.range, JSON.stringify(fixedSource))
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
function handleImportDeclaration(node) {
|
|
1134
|
+
checkSource(node.source);
|
|
1135
|
+
}
|
|
1136
|
+
function handleExportAllDeclaration(node) {
|
|
1137
|
+
checkSource(node.source);
|
|
1138
|
+
}
|
|
1139
|
+
function handleExportNamedDeclaration(node) {
|
|
1140
|
+
if (node.source) checkSource(node.source);
|
|
1141
|
+
}
|
|
1142
|
+
return {
|
|
1143
|
+
ImportDeclaration: handleImportDeclaration,
|
|
1144
|
+
ExportAllDeclaration: handleExportAllDeclaration,
|
|
1145
|
+
ExportNamedDeclaration: handleExportNamedDeclaration
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
};
|
|
1149
|
+
//#endregion
|
|
1150
|
+
//#region src/upstream/effect/no-opaque-instance-fields.ts
|
|
1151
|
+
const SCHEMA_SOURCES = /* @__PURE__ */ new Set(["effect", "effect/Schema"]);
|
|
1152
|
+
const SCHEMA_NAMESPACE_SOURCES = /* @__PURE__ */ new Set(["effect/Schema"]);
|
|
1153
|
+
const rule$1 = {
|
|
1154
|
+
meta: {
|
|
1155
|
+
type: "problem",
|
|
1156
|
+
docs: { description: "Disallow instance members in Schema.Opaque classes" }
|
|
1157
|
+
},
|
|
1158
|
+
create(context) {
|
|
1159
|
+
const schemaIdentifiers = /* @__PURE__ */ new Set();
|
|
1160
|
+
const opaqueIdentifiers = /* @__PURE__ */ new Set();
|
|
1161
|
+
function isSchemaOpaqueExtension(node) {
|
|
1162
|
+
const sc = node.superClass;
|
|
1163
|
+
if (!sc || sc.type !== "CallExpression") return false;
|
|
1164
|
+
const inner = sc.callee;
|
|
1165
|
+
if (!inner || inner.type !== "CallExpression") return false;
|
|
1166
|
+
return isOpaqueCallee(inner.callee);
|
|
1167
|
+
}
|
|
1168
|
+
function isOpaqueCallee(node) {
|
|
1169
|
+
if (!node) return false;
|
|
1170
|
+
if (node.type === "Identifier") return opaqueIdentifiers.has(node.name);
|
|
1171
|
+
if (node.type !== "MemberExpression") return false;
|
|
1172
|
+
if (node.property?.type !== "Identifier" || node.property.name !== "Opaque") return false;
|
|
1173
|
+
return isSchemaObject(node.object);
|
|
1174
|
+
}
|
|
1175
|
+
function isSchemaObject(node) {
|
|
1176
|
+
if (!node) return false;
|
|
1177
|
+
if (node.type === "Identifier") return schemaIdentifiers.has(node.name);
|
|
1178
|
+
return false;
|
|
1179
|
+
}
|
|
1180
|
+
function checkClass(node) {
|
|
1181
|
+
if (!isSchemaOpaqueExtension(node)) return;
|
|
1182
|
+
for (const element of node.body.body) if (element.type === "PropertyDefinition" && !element.static) context.report({
|
|
1183
|
+
node: element,
|
|
1184
|
+
message: "Classes extending Schema.Opaque must not have instance members"
|
|
1185
|
+
});
|
|
1186
|
+
else if (element.type === "MethodDefinition" && !element.static) context.report({
|
|
1187
|
+
node: element,
|
|
1188
|
+
message: "Classes extending Schema.Opaque must not have instance members"
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
return {
|
|
1192
|
+
ImportDeclaration(node) {
|
|
1193
|
+
if (node.importKind === "type") return;
|
|
1194
|
+
const source = node.source.value;
|
|
1195
|
+
if (typeof source !== "string" || !SCHEMA_SOURCES.has(source)) return;
|
|
1196
|
+
for (const specifier of node.specifiers) if (specifier.type === "ImportNamespaceSpecifier") {
|
|
1197
|
+
if (SCHEMA_NAMESPACE_SOURCES.has(source)) schemaIdentifiers.add(specifier.local.name);
|
|
1198
|
+
} else if (specifier.type === "ImportSpecifier" && specifier.importKind !== "type") {
|
|
1199
|
+
if (specifier.imported.type !== "Identifier") continue;
|
|
1200
|
+
const importedName = specifier.imported.name;
|
|
1201
|
+
if (importedName === "Schema") schemaIdentifiers.add(specifier.local.name);
|
|
1202
|
+
else if (importedName === "Opaque") opaqueIdentifiers.add(specifier.local.name);
|
|
1203
|
+
}
|
|
1204
|
+
},
|
|
1205
|
+
ClassDeclaration: checkClass,
|
|
1206
|
+
ClassExpression: checkClass
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
//#endregion
|
|
1211
|
+
//#region src/upstream/effect/no-unused-internal.ts
|
|
1212
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1213
|
+
const sourceExtensions = /* @__PURE__ */ new Set([".ts"]);
|
|
1214
|
+
function getCwd(context) {
|
|
1215
|
+
return context.cwd ?? context.getCwd?.() ?? process.cwd();
|
|
1216
|
+
}
|
|
1217
|
+
function normalizePathName(filename) {
|
|
1218
|
+
return path.resolve(filename).split(path.sep).join("/");
|
|
1219
|
+
}
|
|
1220
|
+
function rangeNode(range) {
|
|
1221
|
+
return { range: [range[0], range[1]] };
|
|
1222
|
+
}
|
|
1223
|
+
function isSourceFile(fileName) {
|
|
1224
|
+
return sourceExtensions.has(path.extname(fileName)) && !fileName.endsWith(".d.ts");
|
|
1225
|
+
}
|
|
1226
|
+
function findSourceFiles(cwd) {
|
|
1227
|
+
const packagesDir = path.join(cwd, "packages");
|
|
1228
|
+
if (!fs.existsSync(packagesDir)) return [];
|
|
1229
|
+
const files = [];
|
|
1230
|
+
const visit = (directory) => {
|
|
1231
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
1232
|
+
const entryPath = path.join(directory, entry.name);
|
|
1233
|
+
if (entry.isDirectory()) {
|
|
1234
|
+
if (entry.name !== "dist" && entry.name !== "build" && entry.name !== "node_modules") visit(entryPath);
|
|
1235
|
+
} else if (entry.isFile() && isSourceFile(entryPath) && normalizePathName(entryPath).includes("/src/")) files.push(entryPath);
|
|
1236
|
+
}
|
|
1237
|
+
};
|
|
1238
|
+
visit(packagesDir);
|
|
1239
|
+
return files;
|
|
1240
|
+
}
|
|
1241
|
+
function findWorkspacePackages(cwd) {
|
|
1242
|
+
const packagesDir = path.join(cwd, "packages");
|
|
1243
|
+
const packages = /* @__PURE__ */ new Map();
|
|
1244
|
+
if (!fs.existsSync(packagesDir)) return packages;
|
|
1245
|
+
const visit = (directory) => {
|
|
1246
|
+
const packageJson = path.join(directory, "package.json");
|
|
1247
|
+
if (fs.existsSync(packageJson)) {
|
|
1248
|
+
try {
|
|
1249
|
+
const json = JSON.parse(fs.readFileSync(packageJson, "utf8"));
|
|
1250
|
+
if (typeof json.name === "string") packages.set(json.name, directory);
|
|
1251
|
+
} catch {}
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== "dist" && entry.name !== "build") visit(path.join(directory, entry.name));
|
|
1255
|
+
};
|
|
1256
|
+
visit(packagesDir);
|
|
1257
|
+
return packages;
|
|
1258
|
+
}
|
|
1259
|
+
function resolveSourceFile(basePath) {
|
|
1260
|
+
const candidates = path.extname(basePath) === ".ts" ? [basePath] : [
|
|
1261
|
+
basePath,
|
|
1262
|
+
`${basePath}.ts`,
|
|
1263
|
+
path.join(basePath, "index.ts")
|
|
1264
|
+
];
|
|
1265
|
+
for (const candidate of candidates) if (fs.existsSync(candidate) && isSourceFile(candidate)) return normalizePathName(candidate);
|
|
1266
|
+
}
|
|
1267
|
+
function resolveModule(source, containingFile, workspacePackages) {
|
|
1268
|
+
if (source.startsWith("./") || source.startsWith("../")) return resolveSourceFile(path.resolve(path.dirname(containingFile), source));
|
|
1269
|
+
let packageName;
|
|
1270
|
+
for (const name of workspacePackages.keys()) if (source === name || source.startsWith(`${name}/`)) {
|
|
1271
|
+
if (packageName === void 0 || name.length > packageName.length) packageName = name;
|
|
1272
|
+
}
|
|
1273
|
+
if (packageName === void 0) return void 0;
|
|
1274
|
+
const packageRoot = workspacePackages.get(packageName);
|
|
1275
|
+
if (packageRoot === void 0) return void 0;
|
|
1276
|
+
if (source === packageName) return resolveSourceFile(path.join(packageRoot, "src", "index.ts"));
|
|
1277
|
+
const subpath = source.slice(packageName.length + 1);
|
|
1278
|
+
return resolveSourceFile(path.join(packageRoot, "src", subpath));
|
|
1279
|
+
}
|
|
1280
|
+
function hasModifier(node, kind) {
|
|
1281
|
+
return ts.canHaveModifiers(node) && (ts.getModifiers(node) ?? []).some((modifier) => modifier.kind === kind);
|
|
1282
|
+
}
|
|
1283
|
+
function hasExportModifier(node) {
|
|
1284
|
+
return hasModifier(node, ts.SyntaxKind.ExportKeyword);
|
|
1285
|
+
}
|
|
1286
|
+
function hasPrivateOrProtectedModifier(node) {
|
|
1287
|
+
return hasModifier(node, ts.SyntaxKind.PrivateKeyword) || hasModifier(node, ts.SyntaxKind.ProtectedKeyword);
|
|
1288
|
+
}
|
|
1289
|
+
function hasInternalJSDoc(node) {
|
|
1290
|
+
return ts.getJSDocTags(node).some((tag) => tag.tagName.getText() === "internal");
|
|
1291
|
+
}
|
|
1292
|
+
function hasInternalApiJSDoc(node) {
|
|
1293
|
+
if (hasInternalJSDoc(node)) return true;
|
|
1294
|
+
if (ts.isVariableDeclaration(node)) return hasInternalJSDoc(node.parent.parent);
|
|
1295
|
+
if (ts.isBindingElement(node) && ts.isVariableDeclaration(node.parent.parent)) return hasInternalJSDoc(node.parent.parent.parent);
|
|
1296
|
+
return false;
|
|
1297
|
+
}
|
|
1298
|
+
function isInternalPath(fileName) {
|
|
1299
|
+
return /\/src\/(?:.*\/)?internal\//.test(normalizePathName(fileName));
|
|
1300
|
+
}
|
|
1301
|
+
function collectBindingNames(name, out) {
|
|
1302
|
+
if (ts.isIdentifier(name)) {
|
|
1303
|
+
out.push(name);
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
for (const element of name.elements) if (ts.isBindingElement(element)) collectBindingNames(element.name, out);
|
|
1307
|
+
}
|
|
1308
|
+
function getTopLevelDeclarationNameNodes(node) {
|
|
1309
|
+
if ((ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isModuleDeclaration(node)) && node.name !== void 0 && ts.isIdentifier(node.name)) return [node.name];
|
|
1310
|
+
if (ts.isVariableStatement(node)) {
|
|
1311
|
+
const names = [];
|
|
1312
|
+
for (const declaration of node.declarationList.declarations) collectBindingNames(declaration.name, names);
|
|
1313
|
+
return names;
|
|
1314
|
+
}
|
|
1315
|
+
return [];
|
|
1316
|
+
}
|
|
1317
|
+
function getDeclarationForName(node, name) {
|
|
1318
|
+
if (!ts.isVariableStatement(node)) return node;
|
|
1319
|
+
let result = node;
|
|
1320
|
+
const visit = (current) => {
|
|
1321
|
+
if (ts.isVariableDeclaration(current) || ts.isBindingElement(current)) {
|
|
1322
|
+
const names = [];
|
|
1323
|
+
collectBindingNames(current.name, names);
|
|
1324
|
+
if (names.includes(name)) {
|
|
1325
|
+
result = current;
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
ts.forEachChild(current, visit);
|
|
1330
|
+
};
|
|
1331
|
+
visit(node.declarationList);
|
|
1332
|
+
return result;
|
|
1333
|
+
}
|
|
1334
|
+
function isImportOrExportBinding(identifier) {
|
|
1335
|
+
const parent = identifier.parent;
|
|
1336
|
+
return ts.isImportSpecifier(parent) && (parent.name === identifier || parent.propertyName === identifier) || ts.isImportClause(parent) && parent.name === identifier || ts.isNamespaceImport(parent) && parent.name === identifier || ts.isExportSpecifier(parent) && (parent.name === identifier || parent.propertyName === identifier);
|
|
1337
|
+
}
|
|
1338
|
+
function isDeclarationName(identifier) {
|
|
1339
|
+
const parent = identifier.parent;
|
|
1340
|
+
return ts.isVariableDeclaration(parent) && parent.name === identifier || ts.isBindingElement(parent) && parent.name === identifier || ts.isParameter(parent) && parent.name === identifier || ts.isFunctionDeclaration(parent) && parent.name === identifier || ts.isClassDeclaration(parent) && parent.name === identifier || ts.isInterfaceDeclaration(parent) && parent.name === identifier || ts.isTypeAliasDeclaration(parent) && parent.name === identifier || ts.isEnumDeclaration(parent) && parent.name === identifier || ts.isEnumMember(parent) && parent.name === identifier || ts.isModuleDeclaration(parent) && parent.name === identifier;
|
|
1341
|
+
}
|
|
1342
|
+
function isNonReferenceName(identifier) {
|
|
1343
|
+
const parent = identifier.parent;
|
|
1344
|
+
return isImportOrExportBinding(identifier) || isDeclarationName(identifier) || ts.isPropertyAccessExpression(parent) && parent.name === identifier || ts.isPropertyAssignment(parent) && parent.name === identifier || ts.isPropertyDeclaration(parent) && parent.name === identifier || ts.isPropertySignature(parent) && parent.name === identifier || ts.isMethodDeclaration(parent) && parent.name === identifier || ts.isMethodSignature(parent) && parent.name === identifier || ts.isGetAccessorDeclaration(parent) && parent.name === identifier || ts.isSetAccessorDeclaration(parent) && parent.name === identifier;
|
|
1345
|
+
}
|
|
1346
|
+
function isInside(node, container) {
|
|
1347
|
+
if (node.getSourceFile() !== container.getSourceFile()) return false;
|
|
1348
|
+
return node.getStart() >= container.getStart() && node.getEnd() <= container.getEnd();
|
|
1349
|
+
}
|
|
1350
|
+
function getExports(fileInfos, fileName, exportName) {
|
|
1351
|
+
if (fileName === void 0) return [];
|
|
1352
|
+
return fileInfos.get(fileName)?.internalExports.get(exportName) ?? [];
|
|
1353
|
+
}
|
|
1354
|
+
function getReExportedInternals(fileInfos, fileInfo, workspacePackages, specifier) {
|
|
1355
|
+
const exportDeclaration = specifier.parent.parent;
|
|
1356
|
+
const importedName = (specifier.propertyName ?? specifier.name).text;
|
|
1357
|
+
if (exportDeclaration.moduleSpecifier !== void 0 && ts.isStringLiteral(exportDeclaration.moduleSpecifier)) return getExports(fileInfos, resolveModule(exportDeclaration.moduleSpecifier.text, fileInfo.sourceFile.fileName, workspacePackages), importedName);
|
|
1358
|
+
const imported = fileInfo.imports.get(importedName);
|
|
1359
|
+
return [...fileInfo.internalExports.get(importedName) ?? [], ...getExports(fileInfos, imported?.fileName, imported?.importedName ?? "")];
|
|
1360
|
+
}
|
|
1361
|
+
function markUsed(exports, node) {
|
|
1362
|
+
for (const internal of exports) if (!isInside(node, internal.declaration)) internal.used = true;
|
|
1363
|
+
}
|
|
1364
|
+
function addFileDiagnostic(diagnosticsByFile, diagnostic) {
|
|
1365
|
+
const normalized = normalizePathName(diagnostic.fileName);
|
|
1366
|
+
const diagnostics = diagnosticsByFile.get(normalized) ?? [];
|
|
1367
|
+
diagnostics.push(diagnostic);
|
|
1368
|
+
diagnosticsByFile.set(normalized, diagnostics);
|
|
1369
|
+
}
|
|
1370
|
+
function addInternalExport(fileInfo, internal) {
|
|
1371
|
+
const exports = fileInfo.internalExports.get(internal.name) ?? [];
|
|
1372
|
+
exports.push(internal);
|
|
1373
|
+
fileInfo.internalExports.set(internal.name, exports);
|
|
1374
|
+
}
|
|
1375
|
+
function collectFileInfo(sourceFile, workspacePackages) {
|
|
1376
|
+
const fileInfo = {
|
|
1377
|
+
fileName: normalizePathName(sourceFile.fileName),
|
|
1378
|
+
sourceFile,
|
|
1379
|
+
imports: /* @__PURE__ */ new Map(),
|
|
1380
|
+
namespaceImports: /* @__PURE__ */ new Map(),
|
|
1381
|
+
internalExports: /* @__PURE__ */ new Map(),
|
|
1382
|
+
publicDeclarations: []
|
|
1383
|
+
};
|
|
1384
|
+
const publicDeclarationSet = /* @__PURE__ */ new Set();
|
|
1385
|
+
const isInternalFile = isInternalPath(sourceFile.fileName);
|
|
1386
|
+
for (const statement of sourceFile.statements) {
|
|
1387
|
+
if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier)) {
|
|
1388
|
+
const importedFile = resolveModule(statement.moduleSpecifier.text, sourceFile.fileName, workspacePackages);
|
|
1389
|
+
const importClause = statement.importClause;
|
|
1390
|
+
if (importedFile === void 0 || importClause === void 0) continue;
|
|
1391
|
+
if (importClause.name !== void 0) fileInfo.imports.set(importClause.name.text, {
|
|
1392
|
+
fileName: importedFile,
|
|
1393
|
+
importedName: "default"
|
|
1394
|
+
});
|
|
1395
|
+
const namedBindings = importClause.namedBindings;
|
|
1396
|
+
if (namedBindings === void 0) continue;
|
|
1397
|
+
if (ts.isNamespaceImport(namedBindings)) fileInfo.namespaceImports.set(namedBindings.name.text, importedFile);
|
|
1398
|
+
else for (const specifier of namedBindings.elements) {
|
|
1399
|
+
const importedName = (specifier.propertyName ?? specifier.name).text;
|
|
1400
|
+
fileInfo.imports.set(specifier.name.text, {
|
|
1401
|
+
fileName: importedFile,
|
|
1402
|
+
importedName
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
continue;
|
|
1406
|
+
}
|
|
1407
|
+
if (!isInternalFile && ts.isExportDeclaration(statement) && !hasInternalApiJSDoc(statement) && statement.exportClause !== void 0 && ts.isNamedExports(statement.exportClause)) {
|
|
1408
|
+
fileInfo.publicDeclarations.push(statement);
|
|
1409
|
+
continue;
|
|
1410
|
+
}
|
|
1411
|
+
if (!hasExportModifier(statement)) continue;
|
|
1412
|
+
const names = getTopLevelDeclarationNameNodes(statement);
|
|
1413
|
+
if (names.length === 0) continue;
|
|
1414
|
+
const statementIsInternal = hasInternalApiJSDoc(statement);
|
|
1415
|
+
for (const name of names) {
|
|
1416
|
+
const declaration = getDeclarationForName(statement, name);
|
|
1417
|
+
if (statementIsInternal || hasInternalApiJSDoc(declaration)) addInternalExport(fileInfo, {
|
|
1418
|
+
fileName: sourceFile.fileName,
|
|
1419
|
+
declaration,
|
|
1420
|
+
name: name.text,
|
|
1421
|
+
range: [name.getStart(sourceFile), name.getEnd()],
|
|
1422
|
+
used: false
|
|
1423
|
+
});
|
|
1424
|
+
else if (!isInternalFile && !publicDeclarationSet.has(statement)) {
|
|
1425
|
+
fileInfo.publicDeclarations.push(statement);
|
|
1426
|
+
publicDeclarationSet.add(statement);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
return fileInfo;
|
|
1431
|
+
}
|
|
1432
|
+
function markNamespaceReference(fileInfos, fileInfo, namespace, name) {
|
|
1433
|
+
markUsed(getExports(fileInfos, fileInfo.namespaceImports.get(namespace.text), name.text), name);
|
|
1434
|
+
}
|
|
1435
|
+
function scanUsage(fileInfos, fileInfo, workspacePackages) {
|
|
1436
|
+
const visit = (node) => {
|
|
1437
|
+
if (ts.isExportSpecifier(node)) markUsed(getReExportedInternals(fileInfos, fileInfo, workspacePackages, node), node);
|
|
1438
|
+
else if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && ts.isIdentifier(node.name)) markNamespaceReference(fileInfos, fileInfo, node.expression, node.name);
|
|
1439
|
+
else if (ts.isQualifiedName(node) && ts.isIdentifier(node.left)) markNamespaceReference(fileInfos, fileInfo, node.left, node.right);
|
|
1440
|
+
else if (ts.isIdentifier(node) && !isNonReferenceName(node)) {
|
|
1441
|
+
markUsed(fileInfo.internalExports.get(node.text) ?? [], node);
|
|
1442
|
+
const imported = fileInfo.imports.get(node.text);
|
|
1443
|
+
markUsed(getExports(fileInfos, imported?.fileName, imported?.importedName ?? ""), node);
|
|
1444
|
+
}
|
|
1445
|
+
ts.forEachChild(node, visit);
|
|
1446
|
+
};
|
|
1447
|
+
visit(fileInfo.sourceFile);
|
|
1448
|
+
}
|
|
1449
|
+
function findInternalReferences(fileInfos, fileInfo, diagnostics, node) {
|
|
1450
|
+
const visit = (current) => {
|
|
1451
|
+
if (ts.isPropertyAccessExpression(current) && ts.isIdentifier(current.expression)) {
|
|
1452
|
+
const importedFile = fileInfo.namespaceImports.get(current.expression.text);
|
|
1453
|
+
for (const internal of getExports(fileInfos, importedFile, current.name.text)) diagnostics.push({
|
|
1454
|
+
fileName: fileInfo.fileName,
|
|
1455
|
+
range: [current.name.getStart(), current.name.getEnd()],
|
|
1456
|
+
message: `Do not reference @internal export "${internal.name}" in a public exported type signature`
|
|
1457
|
+
});
|
|
1458
|
+
} else if (ts.isQualifiedName(current) && ts.isIdentifier(current.left)) {
|
|
1459
|
+
const importedFile = fileInfo.namespaceImports.get(current.left.text);
|
|
1460
|
+
for (const internal of getExports(fileInfos, importedFile, current.right.text)) diagnostics.push({
|
|
1461
|
+
fileName: fileInfo.fileName,
|
|
1462
|
+
range: [current.right.getStart(), current.right.getEnd()],
|
|
1463
|
+
message: `Do not reference @internal export "${internal.name}" in a public exported type signature`
|
|
1464
|
+
});
|
|
1465
|
+
} else if (ts.isIdentifier(current) && !isNonReferenceName(current)) {
|
|
1466
|
+
const localExports = fileInfo.internalExports.get(current.text) ?? [];
|
|
1467
|
+
const imported = fileInfo.imports.get(current.text);
|
|
1468
|
+
const importedExports = getExports(fileInfos, imported?.fileName, imported?.importedName ?? "");
|
|
1469
|
+
for (const internal of [...localExports, ...importedExports]) diagnostics.push({
|
|
1470
|
+
fileName: fileInfo.fileName,
|
|
1471
|
+
range: [current.getStart(), current.getEnd()],
|
|
1472
|
+
message: `Do not reference @internal export "${internal.name}" in a public exported type signature`
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
ts.forEachChild(current, visit);
|
|
1476
|
+
};
|
|
1477
|
+
visit(node);
|
|
1478
|
+
}
|
|
1479
|
+
function scanTypeParameters(fileInfos, fileInfo, diagnostics, typeParameters) {
|
|
1480
|
+
if (typeParameters === void 0) return;
|
|
1481
|
+
for (const typeParameter of typeParameters) {
|
|
1482
|
+
if (typeParameter.constraint !== void 0) findInternalReferences(fileInfos, fileInfo, diagnostics, typeParameter.constraint);
|
|
1483
|
+
if (typeParameter.default !== void 0) findInternalReferences(fileInfos, fileInfo, diagnostics, typeParameter.default);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
function scanParameterTypes(fileInfos, fileInfo, diagnostics, parameters) {
|
|
1487
|
+
for (const parameter of parameters) if (parameter.type !== void 0) findInternalReferences(fileInfos, fileInfo, diagnostics, parameter.type);
|
|
1488
|
+
}
|
|
1489
|
+
function scanFunctionLikeSignature(fileInfos, fileInfo, diagnostics, node) {
|
|
1490
|
+
scanTypeParameters(fileInfos, fileInfo, diagnostics, node.typeParameters);
|
|
1491
|
+
scanParameterTypes(fileInfos, fileInfo, diagnostics, node.parameters);
|
|
1492
|
+
if (node.type !== void 0) findInternalReferences(fileInfos, fileInfo, diagnostics, node.type);
|
|
1493
|
+
}
|
|
1494
|
+
function scanClassMemberSignature(fileInfos, fileInfo, diagnostics, member) {
|
|
1495
|
+
if (hasInternalJSDoc(member)) return;
|
|
1496
|
+
if (hasPrivateOrProtectedModifier(member)) return;
|
|
1497
|
+
if (ts.isMethodDeclaration(member) || ts.isConstructorDeclaration(member) || ts.isGetAccessorDeclaration(member) || ts.isSetAccessorDeclaration(member)) scanFunctionLikeSignature(fileInfos, fileInfo, diagnostics, member);
|
|
1498
|
+
else if (ts.isPropertyDeclaration(member) && member.type !== void 0) findInternalReferences(fileInfos, fileInfo, diagnostics, member.type);
|
|
1499
|
+
}
|
|
1500
|
+
function scanInterfaceMemberSignature(fileInfos, fileInfo, diagnostics, member) {
|
|
1501
|
+
if (hasInternalJSDoc(member)) return;
|
|
1502
|
+
if (ts.isMethodSignature(member) || ts.isCallSignatureDeclaration(member) || ts.isConstructSignatureDeclaration(member)) scanFunctionLikeSignature(fileInfos, fileInfo, diagnostics, member);
|
|
1503
|
+
else if (ts.isPropertySignature(member) && member.type !== void 0) findInternalReferences(fileInfos, fileInfo, diagnostics, member.type);
|
|
1504
|
+
else if (ts.isIndexSignatureDeclaration(member)) scanFunctionLikeSignature(fileInfos, fileInfo, diagnostics, member);
|
|
1505
|
+
}
|
|
1506
|
+
function scanPublicSignature(fileInfos, fileInfo, diagnostics, node, workspacePackages) {
|
|
1507
|
+
if (ts.isFunctionDeclaration(node)) scanFunctionLikeSignature(fileInfos, fileInfo, diagnostics, node);
|
|
1508
|
+
else if (ts.isVariableStatement(node)) {
|
|
1509
|
+
for (const declaration of node.declarationList.declarations) if (declaration.type !== void 0) findInternalReferences(fileInfos, fileInfo, diagnostics, declaration.type);
|
|
1510
|
+
} else if (ts.isTypeAliasDeclaration(node)) {
|
|
1511
|
+
scanTypeParameters(fileInfos, fileInfo, diagnostics, node.typeParameters);
|
|
1512
|
+
findInternalReferences(fileInfos, fileInfo, diagnostics, node.type);
|
|
1513
|
+
} else if (ts.isInterfaceDeclaration(node)) {
|
|
1514
|
+
scanTypeParameters(fileInfos, fileInfo, diagnostics, node.typeParameters);
|
|
1515
|
+
for (const heritage of node.heritageClauses ?? []) for (const type of heritage.types) findInternalReferences(fileInfos, fileInfo, diagnostics, type);
|
|
1516
|
+
for (const member of node.members) scanInterfaceMemberSignature(fileInfos, fileInfo, diagnostics, member);
|
|
1517
|
+
} else if (ts.isClassDeclaration(node)) {
|
|
1518
|
+
scanTypeParameters(fileInfos, fileInfo, diagnostics, node.typeParameters);
|
|
1519
|
+
for (const heritage of node.heritageClauses ?? []) for (const type of heritage.types) findInternalReferences(fileInfos, fileInfo, diagnostics, type);
|
|
1520
|
+
for (const member of node.members) scanClassMemberSignature(fileInfos, fileInfo, diagnostics, member);
|
|
1521
|
+
} else if (ts.isExportDeclaration(node) && node.exportClause !== void 0 && ts.isNamedExports(node.exportClause)) for (const specifier of node.exportClause.elements) for (const internal of getReExportedInternals(fileInfos, fileInfo, workspacePackages, specifier)) {
|
|
1522
|
+
const name = specifier.propertyName ?? specifier.name;
|
|
1523
|
+
diagnostics.push({
|
|
1524
|
+
fileName: fileInfo.fileName,
|
|
1525
|
+
range: [name.getStart(), name.getEnd()],
|
|
1526
|
+
message: `Do not re-export @internal export "${internal.name}" from a public module`
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
function analyze(cwd) {
|
|
1531
|
+
const normalizedCwd = normalizePathName(cwd);
|
|
1532
|
+
const cached = cache.get(normalizedCwd);
|
|
1533
|
+
if (cached !== void 0) return cached;
|
|
1534
|
+
const workspacePackages = findWorkspacePackages(cwd);
|
|
1535
|
+
const fileInfos = /* @__PURE__ */ new Map();
|
|
1536
|
+
for (const fileName of findSourceFiles(cwd)) {
|
|
1537
|
+
const fileInfo = collectFileInfo(ts.createSourceFile(fileName, fs.readFileSync(fileName, "utf8"), ts.ScriptTarget.Latest, true), workspacePackages);
|
|
1538
|
+
fileInfos.set(fileInfo.fileName, fileInfo);
|
|
1539
|
+
}
|
|
1540
|
+
for (const fileInfo of fileInfos.values()) scanUsage(fileInfos, fileInfo, workspacePackages);
|
|
1541
|
+
const diagnosticsByFile = /* @__PURE__ */ new Map();
|
|
1542
|
+
for (const fileInfo of fileInfos.values()) {
|
|
1543
|
+
const diagnostics = [];
|
|
1544
|
+
for (const declaration of fileInfo.publicDeclarations) scanPublicSignature(fileInfos, fileInfo, diagnostics, declaration, workspacePackages);
|
|
1545
|
+
for (const diagnostic of diagnostics) addFileDiagnostic(diagnosticsByFile, diagnostic);
|
|
1546
|
+
for (const exportsForName of fileInfo.internalExports.values()) for (const internal of exportsForName) if (!internal.used) addFileDiagnostic(diagnosticsByFile, {
|
|
1547
|
+
fileName: internal.fileName,
|
|
1548
|
+
range: internal.range,
|
|
1549
|
+
message: `@internal export "${internal.name}" is not used by production sources`
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
for (const diagnostics of diagnosticsByFile.values()) diagnostics.sort((self, that) => self.range[0] - that.range[0]);
|
|
1553
|
+
const analysis = { diagnosticsByFile };
|
|
1554
|
+
cache.set(normalizedCwd, analysis);
|
|
1555
|
+
return analysis;
|
|
1556
|
+
}
|
|
1557
|
+
//#endregion
|
|
1558
|
+
//#region src/index.ts
|
|
1559
|
+
const plugin = {
|
|
1560
|
+
meta: { name: "rules" },
|
|
1561
|
+
rules: {
|
|
1562
|
+
"no-bigint-literals": rule$4,
|
|
1563
|
+
"no-import-from-barrel-package": rule$3,
|
|
1564
|
+
"no-js-extension-imports": rule$2,
|
|
1565
|
+
"no-opaque-instance-fields": rule$1,
|
|
1566
|
+
"no-unused-internal": {
|
|
1567
|
+
meta: {
|
|
1568
|
+
type: "problem",
|
|
1569
|
+
docs: { description: "Disallow unused @internal exports, public re-exports, and references from public type signatures" }
|
|
1570
|
+
},
|
|
1571
|
+
create(context) {
|
|
1572
|
+
const diagnostics = analyze(getCwd(context)).diagnosticsByFile.get(normalizePathName(context.filename)) ?? [];
|
|
1573
|
+
return { Program() {
|
|
1574
|
+
for (const diagnostic of diagnostics) context.report({
|
|
1575
|
+
node: rangeNode(diagnostic.range),
|
|
1576
|
+
message: diagnostic.message
|
|
1577
|
+
});
|
|
1578
|
+
} };
|
|
1579
|
+
}
|
|
1580
|
+
},
|
|
1581
|
+
"consistent-import-extension": rule$32,
|
|
1582
|
+
"require-import-extension": rule$6,
|
|
1583
|
+
"force-array-empty": rule$31,
|
|
1584
|
+
"force-iterable-empty": rule$30,
|
|
1585
|
+
"force-predicate": rule$29,
|
|
1586
|
+
"force-string-empty": rule$28,
|
|
1587
|
+
"force-ts-extension": rule$33,
|
|
1588
|
+
"no-effect-import-as": rule$27,
|
|
1589
|
+
"no-effect-runtime-run": rule$26,
|
|
1590
|
+
"no-effect-subpath-import": rule$25,
|
|
1591
|
+
"no-error-cause-option": rule$24,
|
|
1592
|
+
"no-error-property-access": rule$23,
|
|
1593
|
+
"no-eslint-disable-comments": rule$22,
|
|
1594
|
+
"no-fetch": rule$21,
|
|
1595
|
+
"no-instanceof-error": rule$20,
|
|
1596
|
+
"no-js-date": rule$19,
|
|
1597
|
+
"no-jsx-script-tag": rule$18,
|
|
1598
|
+
"no-let": rule$17,
|
|
1599
|
+
"no-node-imports": rule$16,
|
|
1600
|
+
"no-option-tag-comparison": rule$15,
|
|
1601
|
+
"no-raw-hono-create-middleware": rule$14,
|
|
1602
|
+
"no-redundant-alias": rule$13,
|
|
1603
|
+
"no-string-style": rule$12,
|
|
1604
|
+
"no-sync-decode": rule$11,
|
|
1605
|
+
"no-type-predicate": rule$10,
|
|
1606
|
+
"prefer-is-nullish": rule$9,
|
|
1607
|
+
"prefer-non-unknown-decode": rule$8,
|
|
1608
|
+
"require-disable-reason": rule$7,
|
|
1609
|
+
"require-top-level-decoder": rule$5
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1612
|
+
//#endregion
|
|
1613
|
+
export { compatibilityRuleNames, plugin as default, effectRuleNames, typescriptRuleNames };
|