oxlint-plugin-effector 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/index.js +75 -11
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -12,9 +12,11 @@ checker and passes it.
|
|
|
12
12
|
The Effector rules need TypeScript types (to tell a `Store` from an `Event`, follow units across files, etc.).
|
|
13
13
|
oxlint does not hand type information to JS plugins. So instead of relying on it, **the plugin builds its own
|
|
14
14
|
`ts.Program`** using the `typescript` package directly and calls `getTypeAtLocation` itself, mapping oxlint's AST
|
|
15
|
-
nodes to TS nodes by source range.
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
nodes to TS nodes by source range. This is an oxlint plugin — it does **not** target or support ESLint; under
|
|
16
|
+
oxlint the plugin always constructs its own program. There is **no `@typescript-eslint` in the runtime** — the
|
|
17
|
+
only runtime dependencies are `typescript` (peer) and `esquery`. (The test suite happens to run the rules through
|
|
18
|
+
`@typescript-eslint/rule-tester` to validate against the real type checker, but that's a testing detail, not a
|
|
19
|
+
runtime target.)
|
|
18
20
|
|
|
19
21
|
Program discovery (`src/shared/services.ts`) is on plain `typescript` and handles the awkward setups:
|
|
20
22
|
|
package/dist/index.js
CHANGED
|
@@ -26,22 +26,33 @@ import * as ts from "typescript";
|
|
|
26
26
|
function getParserServices(context) {
|
|
27
27
|
const existing = context.sourceCode?.parserServices;
|
|
28
28
|
if (existing?.program && existing.esTreeNodeToTSNodeMap) return existing;
|
|
29
|
-
|
|
29
|
+
const filename = context.filename ?? context.physicalFilename;
|
|
30
|
+
let services = servicesCache.get(filename);
|
|
31
|
+
if (!services) {
|
|
32
|
+
services = buildServices(context);
|
|
33
|
+
servicesCache.set(filename, services);
|
|
34
|
+
}
|
|
35
|
+
return services;
|
|
30
36
|
}
|
|
37
|
+
var servicesCache = /* @__PURE__ */ new Map();
|
|
31
38
|
var programCache = /* @__PURE__ */ new Map();
|
|
39
|
+
var standaloneCache = /* @__PURE__ */ new Map();
|
|
40
|
+
var findSourceFile = (program, filename) => program.getSourceFile(filename) ?? program.getSourceFiles().find((sf) => norm(sf.fileName) === norm(filename));
|
|
32
41
|
function buildServices(context) {
|
|
33
42
|
const filename = context.filename ?? context.physicalFilename;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (!sourceFile)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
let program = getProgram(filename);
|
|
44
|
+
let sourceFile = findSourceFile(program, filename);
|
|
45
|
+
if (!sourceFile) {
|
|
46
|
+
program = getStandaloneProgram(filename);
|
|
47
|
+
sourceFile = findSourceFile(program, filename);
|
|
48
|
+
}
|
|
49
|
+
if (!sourceFile) return getInertServices();
|
|
50
|
+
const file = sourceFile;
|
|
40
51
|
const checker = program.getTypeChecker();
|
|
41
|
-
const byRange = indexByRange(
|
|
52
|
+
const byRange = indexByRange(file);
|
|
42
53
|
const toTSNode = (node) => {
|
|
43
54
|
const [start, end] = node.range;
|
|
44
|
-
return byRange.get(key(start, end)) ?? deepestContaining(
|
|
55
|
+
return byRange.get(key(start, end)) ?? deepestContaining(file, start, end);
|
|
45
56
|
};
|
|
46
57
|
return {
|
|
47
58
|
program,
|
|
@@ -60,7 +71,14 @@ var FALLBACK_OPTIONS = {
|
|
|
60
71
|
skipLibCheck: true
|
|
61
72
|
};
|
|
62
73
|
var norm = (p) => p.replace(/\\/g, "/");
|
|
63
|
-
var
|
|
74
|
+
var parsedConfigCache = /* @__PURE__ */ new Map();
|
|
75
|
+
var parseConfig = (configPath) => {
|
|
76
|
+
if (parsedConfigCache.has(configPath))
|
|
77
|
+
return parsedConfigCache.get(configPath);
|
|
78
|
+
const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, ts.sys) ?? void 0;
|
|
79
|
+
parsedConfigCache.set(configPath, parsed);
|
|
80
|
+
return parsed;
|
|
81
|
+
};
|
|
64
82
|
function projectContaining(configPath, filename, seen = /* @__PURE__ */ new Set()) {
|
|
65
83
|
if (seen.has(norm(configPath))) return void 0;
|
|
66
84
|
seen.add(norm(configPath));
|
|
@@ -93,6 +111,48 @@ function getProgram(filename) {
|
|
|
93
111
|
programCache.set(cacheKey, program);
|
|
94
112
|
return program;
|
|
95
113
|
}
|
|
114
|
+
function getStandaloneProgram(filename) {
|
|
115
|
+
let program = standaloneCache.get(filename);
|
|
116
|
+
if (!program) {
|
|
117
|
+
program = ts.createProgram({
|
|
118
|
+
rootNames: [filename],
|
|
119
|
+
options: FALLBACK_OPTIONS
|
|
120
|
+
});
|
|
121
|
+
standaloneCache.set(filename, program);
|
|
122
|
+
}
|
|
123
|
+
return program;
|
|
124
|
+
}
|
|
125
|
+
var inertServices;
|
|
126
|
+
function getInertServices() {
|
|
127
|
+
if (inertServices) return inertServices;
|
|
128
|
+
const name = "__oxlint-plugin-effector-empty__.ts";
|
|
129
|
+
const sf = ts.createSourceFile(name, "", ts.ScriptTarget.Latest, true);
|
|
130
|
+
const host = {
|
|
131
|
+
getSourceFile: (f) => norm(f) === norm(name) ? sf : void 0,
|
|
132
|
+
getDefaultLibFileName: () => "lib.d.ts",
|
|
133
|
+
writeFile: () => void 0,
|
|
134
|
+
getCurrentDirectory: () => "",
|
|
135
|
+
getDirectories: () => [],
|
|
136
|
+
getCanonicalFileName: (f) => f,
|
|
137
|
+
useCaseSensitiveFileNames: () => true,
|
|
138
|
+
getNewLine: () => "\n",
|
|
139
|
+
fileExists: (f) => norm(f) === norm(name),
|
|
140
|
+
readFile: () => ""
|
|
141
|
+
};
|
|
142
|
+
const program = ts.createProgram(
|
|
143
|
+
[name],
|
|
144
|
+
{ noLib: true, skipLibCheck: true },
|
|
145
|
+
host
|
|
146
|
+
);
|
|
147
|
+
inertServices = {
|
|
148
|
+
program,
|
|
149
|
+
esTreeNodeToTSNodeMap: { get: () => void 0, has: () => false },
|
|
150
|
+
tsNodeToESTreeNodeMap: { get: () => void 0, has: () => false },
|
|
151
|
+
getTypeAtLocation: () => void 0,
|
|
152
|
+
getSymbolAtLocation: () => void 0
|
|
153
|
+
};
|
|
154
|
+
return inertServices;
|
|
155
|
+
}
|
|
96
156
|
function deepestContaining(sourceFile, start, end) {
|
|
97
157
|
let best = sourceFile;
|
|
98
158
|
const visit = (node) => {
|
|
@@ -156,7 +216,7 @@ var symbolMatches = (symbol, names, from) => {
|
|
|
156
216
|
return declarations.map((decl) => decl.getSourceFile().fileName).some((fname) => fname.includes("node_modules") && fname.includes(from));
|
|
157
217
|
};
|
|
158
218
|
var typeMatches = (type, names, from, depth = 0) => {
|
|
159
|
-
if (depth > 10) return false;
|
|
219
|
+
if (!type || depth > 10) return false;
|
|
160
220
|
const symbol = type.getSymbol() ?? type.aliasSymbol;
|
|
161
221
|
if (symbol && symbolMatches(symbol, names, from)) return true;
|
|
162
222
|
if (type.isUnion() || type.isIntersection())
|
|
@@ -682,6 +742,7 @@ var mandatory_scope_binding_default = createRule({
|
|
|
682
742
|
const inHook = [];
|
|
683
743
|
const isExpectingUnit = (slot) => {
|
|
684
744
|
const tsnode = services.esTreeNodeToTSNodeMap.get(slot);
|
|
745
|
+
if (!tsnode) return false;
|
|
685
746
|
const type = checker.getContextualType(tsnode);
|
|
686
747
|
if (type) return isType.event(type) || isType.effect(type);
|
|
687
748
|
else return false;
|
|
@@ -708,6 +769,7 @@ var mandatory_scope_binding_default = createRule({
|
|
|
708
769
|
const name = nameOf.function(node);
|
|
709
770
|
if (name && UseRegex.test(name.name)) return void inRender.push(true);
|
|
710
771
|
const tsnode = services.esTreeNodeToTSNodeMap.get(node);
|
|
772
|
+
if (!tsnode) return void inRender.push(false);
|
|
711
773
|
const signature = checker.getSignatureFromDeclaration(tsnode);
|
|
712
774
|
const returnType = signature ? checker.getReturnTypeOfSignature(signature) : void 0;
|
|
713
775
|
const isJSX = matchesType(returnType, (type) => isType.jsx(type));
|
|
@@ -1436,6 +1498,7 @@ var no_units_spawn_in_render_default = createRule({
|
|
|
1436
1498
|
if (name && UseRegex2.test(name.name))
|
|
1437
1499
|
return void stack.render.push(true);
|
|
1438
1500
|
const tsnode = services.esTreeNodeToTSNodeMap.get(node);
|
|
1501
|
+
if (!tsnode) return void stack.render.push(false);
|
|
1439
1502
|
const signature = checker.getSignatureFromDeclaration(tsnode);
|
|
1440
1503
|
const returnType = signature ? checker.getReturnTypeOfSignature(signature) : void 0;
|
|
1441
1504
|
const isJSX = matchesType2(returnType, (type) => isType.jsx(type));
|
|
@@ -1559,6 +1622,7 @@ function getCalleeName(callee) {
|
|
|
1559
1622
|
else return null;
|
|
1560
1623
|
}
|
|
1561
1624
|
function hasEffectorUnitInType(ctx, type, depth = 3) {
|
|
1625
|
+
if (!type) return false;
|
|
1562
1626
|
if (isType.unit(type)) return true;
|
|
1563
1627
|
if (depth <= 0) return false;
|
|
1564
1628
|
if (type.isUnion())
|
package/package.json
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oxlint-plugin-effector",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Enforcing best practices for Effector — a full type-aware oxlint plugin (port of eslint-plugin-effector)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Marsel Abazbekov <marsel.ave@gmail.com>",
|
|
5
7
|
"keywords": [
|
|
6
8
|
"oxlint",
|
|
7
9
|
"oxlint-plugin",
|