@geonosis/oxlint-plugin-biological-architecture 2.13.0 → 3.0.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/CHANGELOG.md +6 -0
- package/RULES.md +1 -2
- package/dist/discover-atoms.d.ts +5 -6
- package/dist/discover-atoms.js +33 -24
- package/dist/index.d.ts +7 -54
- package/dist/index.js +180 -7973
- package/dist/presets.d.ts +6 -8
- package/dist/presets.js +137 -6
- package/dist/rules/atom-no-deps.d.ts +21 -0
- package/dist/rules/atom-no-deps.js +29 -0
- package/dist/rules/boundary-time-is-primitive.d.ts +56 -0
- package/dist/rules/boundary-time-is-primitive.js +134 -0
- package/dist/rules/cell-must-be-stateful.d.ts +22 -0
- package/dist/rules/cell-must-be-stateful.js +75 -0
- package/dist/rules/cell-must-not-compose-cell.d.ts +21 -0
- package/dist/rules/cell-must-not-compose-cell.js +29 -0
- package/dist/rules/cell-no-cross-feature-organelles.d.ts +21 -0
- package/dist/rules/cell-no-cross-feature-organelles.js +37 -0
- package/dist/rules/cell-no-tissues.d.ts +21 -0
- package/dist/rules/cell-no-tissues.js +23 -0
- package/dist/rules/cells-folder-index-is-barrel.d.ts +35 -0
- package/dist/rules/cells-folder-index-is-barrel.js +66 -0
- package/dist/rules/compound-must-be-stateless.d.ts +39 -0
- package/dist/rules/compound-must-be-stateless.js +86 -0
- package/dist/rules/compound-no-stores.d.ts +21 -0
- package/dist/rules/compound-no-stores.js +25 -0
- package/dist/rules/constants-in-constants-file.d.ts +41 -0
- package/dist/rules/constants-in-constants-file.js +41 -0
- package/dist/rules/dialect-through-the-seam.d.ts +63 -0
- package/dist/rules/dialect-through-the-seam.js +102 -0
- package/dist/rules/durable-body-reads-through-steps.d.ts +78 -0
- package/dist/rules/durable-body-reads-through-steps.js +122 -0
- package/dist/rules/effect-hook-naming.d.ts +48 -0
- package/dist/rules/effect-hook-naming.js +82 -0
- package/dist/rules/emit-declares-attempts.d.ts +62 -0
- package/dist/rules/emit-declares-attempts.js +102 -0
- package/dist/rules/font-roles-only.d.ts +78 -0
- package/dist/rules/font-roles-only.js +88 -0
- package/dist/rules/layer-walls.d.ts +97 -0
- package/dist/rules/layer-walls.js +138 -0
- package/dist/rules/lib/cells-barrel.d.ts +2 -0
- package/dist/rules/lib/cells-barrel.js +6 -0
- package/dist/rules/lib/colors.d.ts +24 -0
- package/dist/rules/lib/colors.js +261 -0
- package/dist/rules/lib/context.d.ts +26 -0
- package/dist/rules/lib/context.js +0 -0
- package/dist/rules/lib/direction-rule.d.ts +65 -0
- package/dist/rules/lib/direction-rule.js +125 -0
- package/dist/rules/lib/doors.d.ts +26 -0
- package/dist/rules/lib/doors.js +89 -0
- package/dist/rules/lib/hooks.d.ts +20 -0
- package/dist/rules/lib/hooks.js +21 -0
- package/dist/rules/lib/imports.d.ts +52 -0
- package/dist/rules/lib/imports.js +25 -0
- package/dist/rules/lib/landing.d.ts +50 -0
- package/dist/rules/lib/landing.js +111 -0
- package/dist/rules/lib/literal-call.d.ts +11 -0
- package/dist/rules/lib/literal-call.js +10 -0
- package/dist/rules/lib/module-text.d.ts +43 -0
- package/dist/{chunk-H7UQNDFE.js → rules/lib/module-text.js} +44 -30
- package/dist/rules/lib/mutations.d.ts +19 -0
- package/dist/rules/lib/mutations.js +27 -0
- package/dist/rules/lib/nodes.d.ts +25 -0
- package/dist/rules/lib/nodes.js +71 -0
- package/dist/rules/lib/objects.d.ts +17 -0
- package/dist/rules/lib/objects.js +35 -0
- package/dist/rules/lib/options.d.ts +74 -0
- package/dist/rules/lib/options.js +64 -0
- package/dist/rules/lib/package-imports.d.ts +6 -0
- package/dist/rules/lib/package-imports.js +43 -0
- package/dist/rules/lib/paths.d.ts +47 -0
- package/dist/rules/lib/paths.js +28 -0
- package/dist/rules/lib/pattern-match.d.ts +16 -0
- package/dist/rules/lib/pattern-match.js +29 -0
- package/dist/rules/lib/probe.d.ts +55 -0
- package/dist/rules/lib/probe.js +39 -0
- package/dist/rules/lib/reexports.d.ts +20 -0
- package/dist/rules/lib/reexports.js +40 -0
- package/dist/rules/lib/render-tracking.d.ts +54 -0
- package/dist/rules/lib/render-tracking.js +27 -0
- package/dist/rules/lib/schema.d.ts +14 -0
- package/dist/rules/lib/schema.js +11 -0
- package/dist/rules/lib/shared-features.d.ts +24 -0
- package/dist/rules/lib/shared-features.js +16 -0
- package/dist/rules/lib/steps.d.ts +19 -0
- package/dist/rules/lib/steps.js +37 -0
- package/dist/rules/lib/styling.d.ts +65 -0
- package/dist/rules/lib/styling.js +90 -0
- package/dist/rules/lib/tiers.d.ts +31 -0
- package/dist/rules/lib/tiers.js +32 -0
- package/dist/rules/lib/tissue-entrypoint.d.ts +5 -0
- package/dist/rules/lib/tissue-entrypoint.js +15 -0
- package/dist/rules/lib/tsconfig-paths.d.ts +5 -0
- package/dist/rules/lib/tsconfig-paths.js +109 -0
- package/dist/rules/lib/visitors.d.ts +31 -0
- package/dist/rules/lib/visitors.js +38 -0
- package/dist/rules/list-take-without-order.d.ts +36 -0
- package/dist/rules/list-take-without-order.js +84 -0
- package/dist/rules/max-comment-density.d.ts +49 -0
- package/dist/rules/max-comment-density.js +72 -0
- package/dist/rules/molecule-atoms-only.d.ts +21 -0
- package/dist/rules/molecule-atoms-only.js +28 -0
- package/dist/rules/molecule-must-compose.d.ts +25 -0
- package/dist/rules/molecule-must-compose.js +73 -0
- package/dist/rules/mutating-step-declares-retry.d.ts +57 -0
- package/dist/rules/mutating-step-declares-retry.js +92 -0
- package/dist/rules/named-when-block.d.ts +37 -0
- package/dist/rules/named-when-block.js +59 -0
- package/dist/rules/next-route-segment-is-thin-delegate.d.ts +56 -0
- package/dist/rules/next-route-segment-is-thin-delegate.js +194 -0
- package/dist/rules/no-brand-names.d.ts +64 -0
- package/dist/rules/no-brand-names.js +80 -0
- package/dist/rules/no-card-shaped-div.d.ts +67 -0
- package/dist/rules/no-card-shaped-div.js +73 -0
- package/dist/rules/no-cross-feature-stores.d.ts +21 -0
- package/dist/rules/no-cross-feature-stores.js +31 -0
- package/dist/rules/no-d1-transaction.d.ts +69 -0
- package/dist/rules/no-d1-transaction.js +90 -0
- package/dist/rules/no-date-string-compare.d.ts +28 -0
- package/dist/rules/no-date-string-compare.js +100 -0
- package/dist/rules/no-duplicate-jsx-patterns.d.ts +55 -0
- package/dist/rules/no-duplicate-jsx-patterns.js +103 -0
- package/dist/rules/no-generic-utility-package.d.ts +38 -0
- package/dist/rules/no-generic-utility-package.js +86 -0
- package/dist/rules/no-hook-in-component-disguise.d.ts +96 -0
- package/dist/rules/no-hook-in-component-disguise.js +229 -0
- package/dist/rules/no-index-count-as-exact.d.ts +42 -0
- package/dist/rules/no-index-count-as-exact.js +65 -0
- package/dist/rules/no-inert-hidden-jsx.d.ts +40 -0
- package/dist/rules/no-inert-hidden-jsx.js +75 -0
- package/dist/rules/no-inline-data-in-jsx.d.ts +24 -0
- package/dist/rules/no-inline-data-in-jsx.js +71 -0
- package/dist/rules/no-invalid-feature-folders.d.ts +36 -0
- package/dist/rules/no-invalid-feature-folders.js +88 -0
- package/dist/rules/no-loader-side-effect-in-index-file.d.ts +55 -0
- package/dist/rules/no-loader-side-effect-in-index-file.js +77 -0
- package/dist/rules/no-logic-in-component-files.d.ts +49 -0
- package/dist/rules/no-logic-in-component-files.js +105 -0
- package/dist/rules/no-mock-db-in-integration.d.ts +88 -0
- package/dist/rules/no-mock-db-in-integration.js +105 -0
- package/dist/rules/no-orm-outside-db.d.ts +21 -0
- package/dist/rules/no-orm-outside-db.js +37 -0
- package/dist/rules/no-raw-html-atoms.d.ts +82 -0
- package/dist/rules/no-raw-html-atoms.js +91 -0
- package/dist/rules/no-raw-sql-outside-allowed.d.ts +61 -0
- package/dist/rules/no-raw-sql-outside-allowed.js +57 -0
- package/dist/rules/no-react-namespace.d.ts +39 -0
- package/dist/rules/no-react-namespace.js +46 -0
- package/dist/rules/no-renamed-html-props.d.ts +38 -0
- package/dist/rules/no-renamed-html-props.js +80 -0
- package/dist/rules/no-render-prop-reader.d.ts +67 -0
- package/dist/rules/no-render-prop-reader.js +59 -0
- package/dist/rules/no-runtime-dead-affordance.d.ts +69 -0
- package/dist/rules/no-runtime-dead-affordance.js +86 -0
- package/dist/rules/no-swallowed-catch.d.ts +36 -0
- package/dist/rules/no-swallowed-catch.js +92 -0
- package/dist/rules/no-trivial-wrapper-component.d.ts +61 -0
- package/dist/rules/no-trivial-wrapper-component.js +147 -0
- package/dist/rules/no-ts-in-bio-folders.d.ts +52 -0
- package/dist/rules/no-ts-in-bio-folders.js +58 -0
- package/dist/rules/no-type-definitions-in-components.d.ts +33 -0
- package/dist/rules/no-type-definitions-in-components.js +48 -0
- package/dist/rules/no-unregistered-plugin-dir.d.ts +116 -0
- package/dist/rules/no-unregistered-plugin-dir.js +138 -0
- package/dist/rules/no-void-port.d.ts +65 -0
- package/dist/rules/no-void-port.js +53 -0
- package/dist/rules/organelle-dependency.d.ts +21 -0
- package/dist/rules/organelle-dependency.js +37 -0
- package/dist/rules/organelle-single-source.d.ts +19 -0
- package/dist/rules/organelle-single-source.js +97 -0
- package/dist/rules/plugin-route-namespaced.d.ts +79 -0
- package/dist/rules/plugin-route-namespaced.js +137 -0
- package/dist/rules/queries-require-org-scope.d.ts +88 -0
- package/dist/rules/queries-require-org-scope.js +83 -0
- package/dist/rules/queue-loop-is-the-library.d.ts +28 -0
- package/dist/rules/queue-loop-is-the-library.js +35 -0
- package/dist/rules/route-no-inline-mutation.d.ts +81 -0
- package/dist/rules/route-no-inline-mutation.js +125 -0
- package/dist/rules/router-schema-parity.d.ts +60 -0
- package/dist/rules/router-schema-parity.js +95 -0
- package/dist/rules/service-name-not-reserved.d.ts +47 -0
- package/dist/rules/service-name-not-reserved.js +112 -0
- package/dist/rules/ssot-no-inline-facts.d.ts +68 -0
- package/dist/rules/ssot-no-inline-facts.js +221 -0
- package/dist/rules/ssot-no-process-env.d.ts +55 -0
- package/dist/rules/ssot-no-process-env.js +51 -0
- package/dist/rules/step-declares-compensation-or-none.d.ts +34 -0
- package/dist/rules/step-declares-compensation-or-none.js +52 -0
- package/dist/rules/step-opens-its-own-cell.d.ts +82 -0
- package/dist/rules/step-opens-its-own-cell.js +163 -0
- package/dist/rules/store-route-scopes-tenant-data.d.ts +52 -0
- package/dist/rules/store-route-scopes-tenant-data.js +150 -0
- package/dist/rules/subpath-over-barrel.d.ts +67 -0
- package/dist/rules/subpath-over-barrel.js +153 -0
- package/dist/rules/subscriber-declares-id.d.ts +39 -0
- package/dist/rules/subscriber-declares-id.js +79 -0
- package/dist/rules/tables-declare-their-plane.d.ts +44 -0
- package/dist/rules/tables-declare-their-plane.js +64 -0
- package/dist/rules/tenant-tables-carry-org-id.d.ts +66 -0
- package/dist/rules/tenant-tables-carry-org-id.js +64 -0
- package/dist/rules/test-no-clock-polling-for-promise-flips.d.ts +42 -0
- package/dist/rules/test-no-clock-polling-for-promise-flips.js +130 -0
- package/dist/rules/time-through-the-door.d.ts +86 -0
- package/dist/rules/time-through-the-door.js +138 -0
- package/dist/rules/tissue-must-compose.d.ts +41 -0
- package/dist/rules/tissue-must-compose.js +113 -0
- package/dist/rules/tissue-no-data-props.d.ts +27 -0
- package/dist/rules/tissue-no-data-props.js +148 -0
- package/dist/rules/tissue-no-hooks.d.ts +43 -0
- package/dist/rules/tissue-no-hooks.js +72 -0
- package/dist/rules/tissue-no-organelles.d.ts +21 -0
- package/dist/rules/tissue-no-organelles.js +17 -0
- package/dist/rules/tissue-no-stores.d.ts +21 -0
- package/dist/rules/tissue-no-stores.js +17 -0
- package/dist/rules/todo-names-its-plan.d.ts +75 -0
- package/dist/rules/todo-names-its-plan.js +68 -0
- package/dist/rules/tokens-only-colors.d.ts +100 -0
- package/dist/rules/tokens-only-colors.js +129 -0
- package/dist/rules/unique-query-step-name.d.ts +50 -0
- package/dist/rules/unique-query-step-name.js +130 -0
- package/dist/rules/worker-handles-are-scoped.d.ts +65 -0
- package/dist/rules/worker-handles-are-scoped.js +97 -0
- package/dist/rules/zustand-v5-best-practices.d.ts +65 -0
- package/dist/rules/zustand-v5-best-practices.js +107 -0
- package/package.json +4 -4
- package/dist/chunk-ZY3XIRRE.js +0 -145
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// src/rules/lib/doors.ts
|
|
2
|
+
import { existsSync, lstatSync, readFileSync, realpathSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { conditionTargetOf, exportTableOf, fileFor } from "./module-text.js";
|
|
5
|
+
var DEPTH = 8;
|
|
6
|
+
var exportsOf = (file, seen, depth) => {
|
|
7
|
+
if (depth > DEPTH || seen.has(file))
|
|
8
|
+
return [];
|
|
9
|
+
seen.add(file);
|
|
10
|
+
const table = exportTableOf(file);
|
|
11
|
+
if (table === undefined)
|
|
12
|
+
return [];
|
|
13
|
+
const names = [...table.declared, ...table.reexports.keys()];
|
|
14
|
+
for (const star of table.stars) {
|
|
15
|
+
const next = fileFor(file, star);
|
|
16
|
+
if (next !== undefined)
|
|
17
|
+
names.push(...exportsOf(next, seen, depth + 1));
|
|
18
|
+
}
|
|
19
|
+
return names.filter((one) => one !== "default");
|
|
20
|
+
};
|
|
21
|
+
var doorsOf = (manifestPath) => {
|
|
22
|
+
let manifest;
|
|
23
|
+
try {
|
|
24
|
+
manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
25
|
+
} catch {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (typeof manifest.name !== "string")
|
|
29
|
+
return;
|
|
30
|
+
if (typeof manifest.exports !== "object" || manifest.exports === null)
|
|
31
|
+
return;
|
|
32
|
+
const dir = dirname(manifestPath);
|
|
33
|
+
const placed = new Map;
|
|
34
|
+
const contested = new Set;
|
|
35
|
+
for (const [key, value] of Object.entries(manifest.exports)) {
|
|
36
|
+
if (key === "." || !key.startsWith("./") || key.includes("*"))
|
|
37
|
+
continue;
|
|
38
|
+
const target = conditionTargetOf(value);
|
|
39
|
+
if (target === undefined)
|
|
40
|
+
continue;
|
|
41
|
+
const entry = fileFor(join(dir, "package.json"), target);
|
|
42
|
+
if (entry === undefined)
|
|
43
|
+
continue;
|
|
44
|
+
const door = key.slice(2);
|
|
45
|
+
for (const name of new Set(exportsOf(entry, new Set, 0))) {
|
|
46
|
+
if (contested.has(name))
|
|
47
|
+
continue;
|
|
48
|
+
const already = placed.get(name);
|
|
49
|
+
if (already !== undefined && already !== door) {
|
|
50
|
+
placed.delete(name);
|
|
51
|
+
contested.add(name);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
placed.set(name, door);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { homes: placed, name: manifest.name };
|
|
58
|
+
};
|
|
59
|
+
var resolvePackage = (from, name) => {
|
|
60
|
+
let dir = dirname(from);
|
|
61
|
+
for (;; ) {
|
|
62
|
+
const packageDir = join(dir, "node_modules", name);
|
|
63
|
+
const manifest = join(packageDir, "package.json");
|
|
64
|
+
if (existsSync(manifest)) {
|
|
65
|
+
let own = false;
|
|
66
|
+
try {
|
|
67
|
+
own = lstatSync(packageDir).isSymbolicLink() && !realpathSync(packageDir).replaceAll("\\", "/").endsWith(`/node_modules/${name}`);
|
|
68
|
+
} catch {
|
|
69
|
+
own = false;
|
|
70
|
+
}
|
|
71
|
+
if (!own) {
|
|
72
|
+
try {
|
|
73
|
+
own = JSON.parse(readFileSync(manifest, "utf8")).private === true;
|
|
74
|
+
} catch {
|
|
75
|
+
own = false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { manifest, own };
|
|
79
|
+
}
|
|
80
|
+
const parent = dirname(dir);
|
|
81
|
+
if (parent === dir)
|
|
82
|
+
return;
|
|
83
|
+
dir = parent;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
export {
|
|
87
|
+
doorsOf,
|
|
88
|
+
resolvePackage
|
|
89
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type AnyNode } from './nodes.js';
|
|
2
|
+
/**
|
|
3
|
+
* React's own naming convention: a function called `use<Something>` is a hook.
|
|
4
|
+
*
|
|
5
|
+
* Exported because two other rules test a bare callee identifier against this exact pattern
|
|
6
|
+
* without needing `hookCalledBy`'s member-expression reach — spelling the regex a second and a
|
|
7
|
+
* third time drifted nothing here, but there is no reason to trust that forever.
|
|
8
|
+
*/
|
|
9
|
+
export declare const HOOK_NAME: RegExp;
|
|
10
|
+
/**
|
|
11
|
+
* The hook a call names, spelled as the file spelled it — or null when the call is not a hook.
|
|
12
|
+
*
|
|
13
|
+
* Two stateless-tier rules read a call's callee as a bare identifier and nothing else, so
|
|
14
|
+
* `React.useState(0)` and `hooks.useCart()` held state in a tissue and in a compound under both
|
|
15
|
+
* rules at "error": the hook was there, the name was there, and the rule was looking one node too
|
|
16
|
+
* high. A member call is read here too — the object is whatever the file imported the hook
|
|
17
|
+
* through, and the property is the hook. A computed member (`hooks[name]()`) names nothing this
|
|
18
|
+
* can read and is left alone, as every unreadable shape in this plugin is.
|
|
19
|
+
*/
|
|
20
|
+
export declare const hookCalledBy: (node: AnyNode) => string | null;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/rules/lib/hooks.ts
|
|
2
|
+
import { calleeNameOf, propertyNameOf } from "./nodes.js";
|
|
3
|
+
var HOOK_NAME = /^use[A-Z]/;
|
|
4
|
+
var hookCalledBy = (node) => {
|
|
5
|
+
const bare = calleeNameOf(node);
|
|
6
|
+
if (bare !== null)
|
|
7
|
+
return HOOK_NAME.test(bare) ? bare : null;
|
|
8
|
+
const callee = node.callee;
|
|
9
|
+
if (callee === undefined)
|
|
10
|
+
return null;
|
|
11
|
+
const property = propertyNameOf(callee);
|
|
12
|
+
if (property === null || !HOOK_NAME.test(property))
|
|
13
|
+
return null;
|
|
14
|
+
const object = callee.object;
|
|
15
|
+
const through = object?.type === "Identifier" && typeof object.name === "string" ? object.name : null;
|
|
16
|
+
return through === null ? property : `${through}.${property}`;
|
|
17
|
+
};
|
|
18
|
+
export {
|
|
19
|
+
HOOK_NAME,
|
|
20
|
+
hookCalledBy
|
|
21
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a file names another file, read once.
|
|
3
|
+
*
|
|
4
|
+
* Twenty-one rules parsed an import source themselves and each covered a different subset of the
|
|
5
|
+
* five ways it can be written, because each was written by whoever needed one of them that day.
|
|
6
|
+
*/
|
|
7
|
+
export type ModuleNode = {
|
|
8
|
+
arguments?: {
|
|
9
|
+
type?: string;
|
|
10
|
+
value?: unknown;
|
|
11
|
+
}[];
|
|
12
|
+
callee?: {
|
|
13
|
+
name?: string;
|
|
14
|
+
type?: string;
|
|
15
|
+
};
|
|
16
|
+
exportKind?: string;
|
|
17
|
+
importKind?: string;
|
|
18
|
+
source?: {
|
|
19
|
+
type?: string;
|
|
20
|
+
value?: unknown;
|
|
21
|
+
} | null;
|
|
22
|
+
/** What the import brought in, for the rules that judge an import by its names. */
|
|
23
|
+
specifiers?: {
|
|
24
|
+
imported?: {
|
|
25
|
+
name?: string;
|
|
26
|
+
};
|
|
27
|
+
local?: {
|
|
28
|
+
name?: string;
|
|
29
|
+
};
|
|
30
|
+
type?: string;
|
|
31
|
+
}[];
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* The module a node names, or null when it names none — `export const x = 1` names nothing, a call
|
|
35
|
+
* that is not a `require` names nothing, and `import(`./${name}`)` names something no rule can
|
|
36
|
+
* read. A rule that guessed at the last one would report a finding whose source it cannot print.
|
|
37
|
+
*/
|
|
38
|
+
export declare const sourceOf: (node: ModuleNode) => string | null;
|
|
39
|
+
/** `import type X from`, `import { … } from` graded type, and `export type … from`. */
|
|
40
|
+
export declare const isTypeOnly: (node: ModuleNode) => boolean;
|
|
41
|
+
/** Whether a specifier is relative — walked from the importer, never resolved through an alias. */
|
|
42
|
+
export declare const isRelative: (source: string) => boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Where a relative source lands, as a path — and any other source unchanged.
|
|
45
|
+
*
|
|
46
|
+
* `../../billing/organelles/totals` carries no feature name and no tier name until it is resolved
|
|
47
|
+
* against the file that wrote it. Two rules did that and the rest did not, so the same breach was a
|
|
48
|
+
* finding or not depending on which rule happened to look at it. `@/features/x` is NOT relative: it
|
|
49
|
+
* is an alias the bundler resolves, and joining it to the importer's directory would invent a path
|
|
50
|
+
* that exists nowhere.
|
|
51
|
+
*/
|
|
52
|
+
export declare const resolveRelative: (importer: string, source: string) => string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/rules/lib/imports.ts
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { literalNamedBy } from "./literal-call.js";
|
|
4
|
+
import { normalise } from "./paths.js";
|
|
5
|
+
var REQUIRE = "require";
|
|
6
|
+
var sourceOf = (node) => {
|
|
7
|
+
const source = node.source;
|
|
8
|
+
if (source === undefined || source === null) {
|
|
9
|
+
if (node.callee === undefined)
|
|
10
|
+
return null;
|
|
11
|
+
return literalNamedBy(node, REQUIRE);
|
|
12
|
+
}
|
|
13
|
+
if (source.type !== undefined && source.type !== "Literal")
|
|
14
|
+
return null;
|
|
15
|
+
return typeof source.value === "string" ? source.value : null;
|
|
16
|
+
};
|
|
17
|
+
var isTypeOnly = (node) => node.importKind === "type" || node.exportKind === "type";
|
|
18
|
+
var isRelative = (source) => source === "." || source === ".." || source.startsWith("./") || source.startsWith("../");
|
|
19
|
+
var resolveRelative = (importer, source) => isRelative(source) ? path.posix.normalize(path.posix.join(path.posix.dirname(normalise(importer)), source)) : source;
|
|
20
|
+
export {
|
|
21
|
+
isRelative,
|
|
22
|
+
isTypeOnly,
|
|
23
|
+
resolveRelative,
|
|
24
|
+
sourceOf
|
|
25
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where an import LANDS, and where the code behind each name it takes LIVES.
|
|
3
|
+
*
|
|
4
|
+
* Two consumer issues were one blindness. A direction rule read the specifier as a string and
|
|
5
|
+
* stopped, so `@/components/organelles` — a bare barrel, which names no file in a tier folder —
|
|
6
|
+
* was invisible (#4), and a one-line `export { CustomCarousel } from '@/kit/organelles/carousel'`
|
|
7
|
+
* sitting in `cells/` laundered an organelle into the cell tier for every rule that looked (#11).
|
|
8
|
+
* The Store adoption that found #4 read 78 → 8 → 5 → 1 as tier fixes landed, which is to say its
|
|
9
|
+
* early numbers measured the rules' blindness as much as the code.
|
|
10
|
+
*
|
|
11
|
+
* So the specifier is resolved on disk: relative to the importer, through the repo's own
|
|
12
|
+
* `package.json` `imports` for a `#` subpath, or through its `tsconfig.json` (or `jsconfig.json`)
|
|
13
|
+
* `paths` — never an alias this plugin knows (LAW 6). The file it lands on is the
|
|
14
|
+
* DOOR. Each name the import takes is then followed through `export … from` and `export *` to the
|
|
15
|
+
* file that declares it, the HOME, to a depth of eight and never twice through one file. A name
|
|
16
|
+
* the door does not export and a bare package are left at the door; a specifier nothing on disk
|
|
17
|
+
* answers lands nowhere, and the rule judges the spelling as before. An import that takes the
|
|
18
|
+
* whole module — a namespace, `export *`, `import()`, `require()` — takes every name the door
|
|
19
|
+
* carries (row 593), narrowed to the members the file reaches when it is a namespace.
|
|
20
|
+
*/
|
|
21
|
+
type Landings = {
|
|
22
|
+
/** The file the specifier resolves to, absolute — or nowhere. */
|
|
23
|
+
door: string | undefined;
|
|
24
|
+
/** Imported name → the absolute file whose code it is; the door when it could not be followed. */
|
|
25
|
+
homes: Map<string, string>;
|
|
26
|
+
};
|
|
27
|
+
/** The absolute file a specifier names from a file, or undefined when nothing on disk answers. */
|
|
28
|
+
export declare const resolveSource: (importer: string, source: string) => string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Every name a module exports, its `export *` modules followed to the same depth as a home is. A
|
|
31
|
+
* namespace import, an `export *`, `import()` and `require()` take the whole module (row 593), and
|
|
32
|
+
* "the whole module" is this list — each name then lands where its code lives, as a named import's
|
|
33
|
+
* would.
|
|
34
|
+
*/
|
|
35
|
+
export declare const exportedNamesOf: (file: string) => Set<string>;
|
|
36
|
+
export declare const landingsOf: (importer: string, source: string, names: readonly string[]) => Landings;
|
|
37
|
+
/**
|
|
38
|
+
* Whether a specifier's last segment IS a tier folder — `@/components/organelles`, `../cells`.
|
|
39
|
+
* A folder named `organelles` is the organelles tier whichever alias reaches it, so when nothing
|
|
40
|
+
* on disk answers the spelling is still judged as the folder it names.
|
|
41
|
+
*/
|
|
42
|
+
export declare const namesATierFolder: (source: string) => boolean;
|
|
43
|
+
/**
|
|
44
|
+
* The root a landed path is judged from: the nearest directory above the importer holding a
|
|
45
|
+
* `package.json` or a `tsconfig.json`, else the importer's own. An absolute path would be judged
|
|
46
|
+
* with every directory above the repo in it, and a checkout under `/app/` or `/home/cells/` would
|
|
47
|
+
* make every resolved import a breach.
|
|
48
|
+
*/
|
|
49
|
+
export declare const rootOf: (file: string) => string;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// src/rules/lib/landing.ts
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { isRelative } from "./imports.js";
|
|
5
|
+
import { exportTableOf, fileAt } from "./module-text.js";
|
|
6
|
+
import { importCandidates } from "./package-imports.js";
|
|
7
|
+
import { TIERS } from "./tiers.js";
|
|
8
|
+
import { aliasCandidates } from "./tsconfig-paths.js";
|
|
9
|
+
var DEPTH = 8;
|
|
10
|
+
var resolved = new Map;
|
|
11
|
+
var find = (importer, source) => {
|
|
12
|
+
if (isRelative(source))
|
|
13
|
+
return fileAt(resolve(dirname(importer), source));
|
|
14
|
+
if (source.startsWith("/") || source.startsWith("."))
|
|
15
|
+
return;
|
|
16
|
+
for (const candidate of [
|
|
17
|
+
...importCandidates(importer, source),
|
|
18
|
+
...aliasCandidates(importer, source)
|
|
19
|
+
]) {
|
|
20
|
+
const found = fileAt(candidate);
|
|
21
|
+
if (found !== undefined)
|
|
22
|
+
return found;
|
|
23
|
+
}
|
|
24
|
+
return;
|
|
25
|
+
};
|
|
26
|
+
var resolveSource = (importer, source) => {
|
|
27
|
+
const key = `${dirname(importer)}\x00${source}`;
|
|
28
|
+
if (!resolved.has(key))
|
|
29
|
+
resolved.set(key, find(importer, source));
|
|
30
|
+
return resolved.get(key);
|
|
31
|
+
};
|
|
32
|
+
var homeOf = (file, name, seen, depth) => {
|
|
33
|
+
if (depth > DEPTH || seen.has(file))
|
|
34
|
+
return;
|
|
35
|
+
seen.add(file);
|
|
36
|
+
const table = exportTableOf(file);
|
|
37
|
+
if (table === undefined)
|
|
38
|
+
return;
|
|
39
|
+
if (table.declared.has(name))
|
|
40
|
+
return file;
|
|
41
|
+
const taken = table.reexports.get(name);
|
|
42
|
+
if (taken !== undefined) {
|
|
43
|
+
const next = resolveSource(file, taken.source);
|
|
44
|
+
return next === undefined ? file : homeOf(next, taken.name, seen, depth + 1) ?? next;
|
|
45
|
+
}
|
|
46
|
+
for (const star of table.stars) {
|
|
47
|
+
const next = resolveSource(file, star);
|
|
48
|
+
if (next === undefined)
|
|
49
|
+
continue;
|
|
50
|
+
const found = homeOf(next, name, seen, depth + 1);
|
|
51
|
+
if (found !== undefined)
|
|
52
|
+
return found;
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
};
|
|
56
|
+
var collectNames = (file, into, seen, depth) => {
|
|
57
|
+
if (depth > DEPTH || seen.has(file))
|
|
58
|
+
return;
|
|
59
|
+
seen.add(file);
|
|
60
|
+
const table = exportTableOf(file);
|
|
61
|
+
if (table === undefined)
|
|
62
|
+
return;
|
|
63
|
+
for (const name of table.declared)
|
|
64
|
+
into.add(name);
|
|
65
|
+
for (const name of table.reexports.keys())
|
|
66
|
+
into.add(name);
|
|
67
|
+
for (const star of table.stars) {
|
|
68
|
+
const next = resolveSource(file, star);
|
|
69
|
+
if (next !== undefined)
|
|
70
|
+
collectNames(next, into, seen, depth + 1);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var exportedNamesOf = (file) => {
|
|
74
|
+
const names = new Set;
|
|
75
|
+
collectNames(file, names, new Set, 0);
|
|
76
|
+
return names;
|
|
77
|
+
};
|
|
78
|
+
var landingsOf = (importer, source, names) => {
|
|
79
|
+
const door = resolveSource(importer, source);
|
|
80
|
+
const homes = new Map;
|
|
81
|
+
if (door === undefined)
|
|
82
|
+
return { door, homes };
|
|
83
|
+
for (const name of names)
|
|
84
|
+
homes.set(name, homeOf(door, name, new Set, 0) ?? door);
|
|
85
|
+
return { door, homes };
|
|
86
|
+
};
|
|
87
|
+
var TIER_FOLDER = new RegExp(`(?:^|/)(?:${TIERS.join("|")})$`);
|
|
88
|
+
var namesATierFolder = (source) => TIER_FOLDER.test(source);
|
|
89
|
+
var roots = new Map;
|
|
90
|
+
var rootAbove = (dir) => {
|
|
91
|
+
const known = roots.get(dir);
|
|
92
|
+
if (known !== undefined || roots.has(dir))
|
|
93
|
+
return known;
|
|
94
|
+
let found;
|
|
95
|
+
if (existsSync(join(dir, "package.json")) || existsSync(join(dir, "tsconfig.json")))
|
|
96
|
+
found = dir;
|
|
97
|
+
else {
|
|
98
|
+
const parent = dirname(dir);
|
|
99
|
+
found = parent === dir ? undefined : rootAbove(parent);
|
|
100
|
+
}
|
|
101
|
+
roots.set(dir, found);
|
|
102
|
+
return found;
|
|
103
|
+
};
|
|
104
|
+
var rootOf = (file) => rootAbove(dirname(file)) ?? dirname(file);
|
|
105
|
+
export {
|
|
106
|
+
exportedNamesOf,
|
|
107
|
+
landingsOf,
|
|
108
|
+
namesATierFolder,
|
|
109
|
+
resolveSource,
|
|
110
|
+
rootOf
|
|
111
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/rules/lib/literal-call.ts
|
|
2
|
+
var literalNamedBy = (node, callee) => {
|
|
3
|
+
if (node.callee.type !== "Identifier" || node.callee.name !== callee)
|
|
4
|
+
return null;
|
|
5
|
+
const [name] = node.arguments ?? [];
|
|
6
|
+
return name?.type === "Literal" && typeof name.value === "string" ? name.value : null;
|
|
7
|
+
};
|
|
8
|
+
export {
|
|
9
|
+
literalNamedBy
|
|
10
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a module's TEXT says it exports, read with regexes and never a parser — the same reading
|
|
3
|
+
* `doors.ts` did for a package's `exports` map, shared now with the landing resolver, because a
|
|
4
|
+
* barrel and a re-export shim are the same shape read for a different question.
|
|
5
|
+
*
|
|
6
|
+
* A table is read once per file per process: `create` runs once per FILE, and the tree does not
|
|
7
|
+
* change under a lint run.
|
|
8
|
+
*/
|
|
9
|
+
type ExportTable = {
|
|
10
|
+
/** Names whose code is HERE: declarations, `export { A as B }` with no `from`, `default`. */
|
|
11
|
+
declared: Set<string>;
|
|
12
|
+
/** Exported name → the name and module it is taken from: `export { A as B } from './x'`. */
|
|
13
|
+
reexports: Map<string, {
|
|
14
|
+
name: string;
|
|
15
|
+
source: string;
|
|
16
|
+
}>;
|
|
17
|
+
/** The modules `export * from` hands through, in order. */
|
|
18
|
+
stars: string[];
|
|
19
|
+
};
|
|
20
|
+
export declare const EXTENSIONS: string[];
|
|
21
|
+
/**
|
|
22
|
+
* Source with its comments blanked to spaces, strings stepped over whole — a glob like `'src/*'`
|
|
23
|
+
* opens no comment and a URL keeps its `//`. Blanked, not cut: the tokens either side of a comment
|
|
24
|
+
* stay apart and every line keeps its number. Every text reader in the plugin reads through it.
|
|
25
|
+
*/
|
|
26
|
+
export declare const stripComments: (source: string) => string;
|
|
27
|
+
/** The file at a base path: itself, with an extension, or its index — or nothing on disk. */
|
|
28
|
+
export declare const fileAt: (base: string) => string | undefined;
|
|
29
|
+
/** A relative module specifier as the file it names, or undefined when nothing on disk answers. */
|
|
30
|
+
export declare const fileFor: (from: string, specifier: string) => string | undefined;
|
|
31
|
+
/** The export table of a file, or undefined when it cannot be read. */
|
|
32
|
+
export declare const exportTableOf: (file: string) => ExportTable | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* The file an `exports` or `imports` value points at, whatever conditions it is wrapped in — a
|
|
35
|
+
* string, an array of alternatives, or a conditions object read in the order a bundler would.
|
|
36
|
+
*/
|
|
37
|
+
export declare const conditionTargetOf: (value: unknown) => string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* JSON as `tsconfig.json` writes it: comments and trailing commas allowed. `JSON.parse` refuses
|
|
40
|
+
* both, and a reader that stripped `//` blindly would cut `"$schema": "https://…"` in half.
|
|
41
|
+
*/
|
|
42
|
+
export declare const parseLooseJson: (text: string) => unknown;
|
|
43
|
+
export {};
|
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
// src/rules/lib/module-text.ts
|
|
2
|
-
import { existsSync, readFileSync, statSync } from "fs";
|
|
3
|
-
import { dirname, resolve } from "path";
|
|
2
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
4
|
var EXTENSIONS = [".ts", ".tsx", ".mts", ".js", ".jsx", ".mjs", ".cjs", ".d.ts"];
|
|
5
5
|
var INDEXES = EXTENSIONS.map((extension) => `/index${extension}`);
|
|
6
|
-
var QUOTES =
|
|
6
|
+
var QUOTES = new Set(['"', "'", "`"]);
|
|
7
7
|
var stripComments = (source) => {
|
|
8
8
|
let out = "";
|
|
9
9
|
let quote;
|
|
10
|
-
for (let at = 0;
|
|
10
|
+
for (let at = 0;at < source.length; at += 1) {
|
|
11
11
|
const char = source[at] ?? "";
|
|
12
|
-
if (quote !==
|
|
12
|
+
if (quote !== undefined) {
|
|
13
13
|
out += char;
|
|
14
14
|
if (char === "\\") {
|
|
15
15
|
out += source[at + 1] ?? "";
|
|
16
16
|
at += 1;
|
|
17
|
-
} else if (char === quote)
|
|
17
|
+
} else if (char === quote)
|
|
18
|
+
quote = undefined;
|
|
18
19
|
} else if (QUOTES.has(char)) {
|
|
19
20
|
quote = char;
|
|
20
21
|
out += char;
|
|
21
22
|
} else if (char === "/" && (source[at + 1] === "/" || source[at + 1] === "*")) {
|
|
22
23
|
const block = source[at + 1] === "*";
|
|
23
|
-
const found = block ? source.indexOf("*/", at + 2) : source.indexOf(
|
|
24
|
+
const found = block ? source.indexOf("*/", at + 2) : source.indexOf(`
|
|
25
|
+
`, at);
|
|
24
26
|
const end = found === -1 ? source.length : block ? found + 2 : found;
|
|
25
27
|
out += source.slice(at, end).replaceAll(/[^\n]/g, " ");
|
|
26
28
|
at = end - 1;
|
|
27
|
-
} else
|
|
29
|
+
} else
|
|
30
|
+
out += char;
|
|
28
31
|
}
|
|
29
32
|
return out;
|
|
30
33
|
};
|
|
@@ -46,64 +49,75 @@ var entriesOf = (list) => list.split(",").map((one) => one.trim().replace(/^type
|
|
|
46
49
|
const [taken, exported] = one.split(/\s+as\s+/);
|
|
47
50
|
return { exported: exported ?? taken ?? "", taken: taken ?? "" };
|
|
48
51
|
});
|
|
49
|
-
var tables =
|
|
52
|
+
var tables = new Map;
|
|
50
53
|
var readTable = (file) => {
|
|
51
54
|
let source;
|
|
52
55
|
try {
|
|
53
56
|
source = stripComments(readFileSync(file, "utf8"));
|
|
54
57
|
} catch {
|
|
55
|
-
return
|
|
58
|
+
return;
|
|
56
59
|
}
|
|
57
|
-
const declared =
|
|
58
|
-
const reexports =
|
|
60
|
+
const declared = new Set;
|
|
61
|
+
const reexports = new Map;
|
|
59
62
|
const stars = [];
|
|
60
|
-
for (const found of source.matchAll(DECLARED))
|
|
61
|
-
|
|
63
|
+
for (const found of source.matchAll(DECLARED))
|
|
64
|
+
declared.add(found[1] ?? "");
|
|
65
|
+
if (DEFAULT.test(source))
|
|
66
|
+
declared.add("default");
|
|
62
67
|
for (const found of source.matchAll(LISTED)) {
|
|
63
68
|
const from = found[2];
|
|
64
69
|
for (const { exported, taken } of entriesOf(found[1] ?? "")) {
|
|
65
|
-
if (from ===
|
|
66
|
-
|
|
70
|
+
if (from === undefined)
|
|
71
|
+
declared.add(exported);
|
|
72
|
+
else
|
|
73
|
+
reexports.set(exported, { name: taken, source: from });
|
|
67
74
|
}
|
|
68
75
|
}
|
|
69
76
|
for (const found of source.matchAll(STAR)) {
|
|
70
77
|
const alias = found[1];
|
|
71
|
-
if (alias !==
|
|
72
|
-
|
|
78
|
+
if (alias !== undefined)
|
|
79
|
+
declared.add(alias);
|
|
80
|
+
else
|
|
81
|
+
stars.push(found[2] ?? "");
|
|
73
82
|
}
|
|
74
83
|
declared.delete("");
|
|
75
84
|
return { declared, reexports, stars };
|
|
76
85
|
};
|
|
77
86
|
var exportTableOf = (file) => {
|
|
78
|
-
if (!tables.has(file))
|
|
87
|
+
if (!tables.has(file))
|
|
88
|
+
tables.set(file, readTable(file));
|
|
79
89
|
return tables.get(file);
|
|
80
90
|
};
|
|
81
91
|
var CONDITIONS = ["source", "import", "default", "types", "require", "node", "browser"];
|
|
82
92
|
var conditionTargetOf = (value) => {
|
|
83
|
-
if (typeof value === "string")
|
|
84
|
-
|
|
85
|
-
if (
|
|
93
|
+
if (typeof value === "string")
|
|
94
|
+
return value;
|
|
95
|
+
if (Array.isArray(value))
|
|
96
|
+
return value.map(conditionTargetOf).find((one) => one !== undefined);
|
|
97
|
+
if (typeof value !== "object" || value === null)
|
|
98
|
+
return;
|
|
86
99
|
const conditions = value;
|
|
87
100
|
for (const condition of CONDITIONS) {
|
|
88
101
|
const found = conditionTargetOf(conditions[condition]);
|
|
89
|
-
if (found !==
|
|
102
|
+
if (found !== undefined)
|
|
103
|
+
return found;
|
|
90
104
|
}
|
|
91
|
-
return Object.values(conditions).map(conditionTargetOf).find((one) => one !==
|
|
105
|
+
return Object.values(conditions).map(conditionTargetOf).find((one) => one !== undefined);
|
|
92
106
|
};
|
|
93
107
|
var parseLooseJson = (text) => {
|
|
94
108
|
const out = stripComments(text);
|
|
95
109
|
try {
|
|
96
110
|
return JSON.parse(out.replaceAll(/,\s*([}\]])/g, "$1"));
|
|
97
111
|
} catch {
|
|
98
|
-
return
|
|
112
|
+
return;
|
|
99
113
|
}
|
|
100
114
|
};
|
|
101
|
-
|
|
102
115
|
export {
|
|
103
|
-
|
|
116
|
+
EXTENSIONS,
|
|
117
|
+
conditionTargetOf,
|
|
118
|
+
exportTableOf,
|
|
104
119
|
fileAt,
|
|
105
120
|
fileFor,
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
parseLooseJson
|
|
121
|
+
parseLooseJson,
|
|
122
|
+
stripComments
|
|
109
123
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type AnyNode } from './nodes.js';
|
|
2
|
+
/**
|
|
3
|
+
* What a WRITE is, in one place. `mutating-step-declares-retry` asks it of a step's invoke body and
|
|
4
|
+
* `route-no-inline-mutation` asks it of a route handler; two copies of this, spelled slightly
|
|
5
|
+
* differently, is how the two rules would come to disagree about which calls are writes — the same
|
|
6
|
+
* drift `lib/steps.ts` exists to prevent for what a step is.
|
|
7
|
+
*
|
|
8
|
+
* The CRUD names `MedusaService` generates. A framework fact, so a default rather than a blank.
|
|
9
|
+
*/
|
|
10
|
+
export declare const DEFAULT_MUTATION_PREFIXES: string[];
|
|
11
|
+
export declare const mutationPrefixesOf: (rule: string, configured: string[] | undefined) => string[];
|
|
12
|
+
/**
|
|
13
|
+
* `svc.createOrder(…)` — a generated CRUD write, told from `listOrders` by the capital after the
|
|
14
|
+
* prefix, and from `createOrderWorkflow(scope)` by being a METHOD on something. A bare identifier
|
|
15
|
+
* call is the workflow door itself, which is the shape both rules exist to send people towards.
|
|
16
|
+
*/
|
|
17
|
+
export declare const writesWith: (prefixes: readonly string[]) => (node: AnyNode) => boolean;
|
|
18
|
+
/** The method name a write call carries, for a message that points at something. */
|
|
19
|
+
export declare const writeNameOf: (node: AnyNode) => string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/rules/lib/mutations.ts
|
|
2
|
+
import { propertyNameOf } from "./nodes.js";
|
|
3
|
+
import { requireOption } from "./options.js";
|
|
4
|
+
var DEFAULT_MUTATION_PREFIXES = [
|
|
5
|
+
"create",
|
|
6
|
+
"update",
|
|
7
|
+
"upsert",
|
|
8
|
+
"delete",
|
|
9
|
+
"softDelete",
|
|
10
|
+
"restore"
|
|
11
|
+
];
|
|
12
|
+
var mutationPrefixesOf = (rule, configured) => requireOption(rule, configured ?? DEFAULT_MUTATION_PREFIXES, "mutationPrefixes");
|
|
13
|
+
var writesWith = (prefixes) => (node) => {
|
|
14
|
+
if (node.type !== "CallExpression")
|
|
15
|
+
return false;
|
|
16
|
+
const property = propertyNameOf(node.callee ?? {});
|
|
17
|
+
if (property === null)
|
|
18
|
+
return false;
|
|
19
|
+
return prefixes.some((prefix) => property.startsWith(prefix) && /^[A-Z]/.test(property.slice(prefix.length)));
|
|
20
|
+
};
|
|
21
|
+
var writeNameOf = (node) => propertyNameOf(node.callee ?? {}) ?? "a service write";
|
|
22
|
+
export {
|
|
23
|
+
DEFAULT_MUTATION_PREFIXES,
|
|
24
|
+
mutationPrefixesOf,
|
|
25
|
+
writeNameOf,
|
|
26
|
+
writesWith
|
|
27
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type AnyNode = {
|
|
2
|
+
type?: string;
|
|
3
|
+
} & Record<string, unknown>;
|
|
4
|
+
/**
|
|
5
|
+
* Every node under this one, this one included.
|
|
6
|
+
*
|
|
7
|
+
* A visitor is handed the node it asked for, and some facts live in its BODY — whether a step's
|
|
8
|
+
* invoke function writes anything is the one that made this necessary, and it can be behind an
|
|
9
|
+
* `if`, inside a `try`, or two closures down. A rule that only read the statements directly under
|
|
10
|
+
* the arrow would pass every step that guarded its write. Children are visited in source order.
|
|
11
|
+
*
|
|
12
|
+
* `parent` is skipped: oxlint hands nodes with back-references, and following one walks the whole
|
|
13
|
+
* program from wherever it started.
|
|
14
|
+
*/
|
|
15
|
+
export declare const walkNode: (node: unknown, visit: (node: AnyNode) => void, descends?: (node: AnyNode) => boolean) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Every node under a function's OWN body, stopping at a nested function — a hook called inside a
|
|
18
|
+
* closure this function merely defines belongs to that closure, not to this one. Two rules walked
|
|
19
|
+
* this by hand, one to find a hook call and one to collect `return` statements.
|
|
20
|
+
*/
|
|
21
|
+
export declare const walkOwnFunctionBody: (body: unknown, visit: (node: AnyNode) => void) => void;
|
|
22
|
+
/** The name a `MemberExpression`'s non-computed property carries, or null. */
|
|
23
|
+
export declare const propertyNameOf: (node: AnyNode) => string | null;
|
|
24
|
+
/** The name an `Identifier` callee carries, or null — a method call is never one of these. */
|
|
25
|
+
export declare const calleeNameOf: (node: AnyNode) => string | null;
|