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.
Files changed (3) hide show
  1. package/README.md +5 -3
  2. package/dist/index.js +75 -11
  3. 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. Under ESLint / the test runner it transparently uses the parser's existing
16
- services; under oxlint it constructs its own. Same rule code, real types, either way. There is **no
17
- `@typescript-eslint` in the runtime** — the only runtime dependencies are `typescript` (peer) and `esquery`.
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
- return buildServices(context);
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
- const program = getProgram(filename);
35
- const sourceFile = program.getSourceFile(filename);
36
- if (!sourceFile)
37
- throw new Error(
38
- `oxlint-plugin-effector: cannot load "${filename}" into a TypeScript program`
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(sourceFile);
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(sourceFile, start, end);
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 parseConfig = (configPath) => ts.getParsedCommandLineOfConfigFile(configPath, {}, ts.sys) ?? void 0;
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.1",
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",