@geonosis/oxlint-plugin-biological-architecture 3.2.0 → 3.3.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 +13 -0
- package/README.md +27 -1
- package/RULES.md +7 -7
- package/corpus/medusa/queries/bad-silent-cap.ts +11 -0
- package/corpus/medusa/queries/good-paged-through.ts +19 -0
- package/corpus/medusa/queries/integration-tests/any-row.ts +10 -0
- package/dist/index.js +3 -1
- package/dist/presets.js +7 -2
- package/dist/rules/lib/direction-rule.d.ts +1 -0
- package/dist/rules/lib/direction-rule.js +13 -1
- package/dist/rules/lib/doors.js +4 -2
- package/dist/rules/lib/fresh.d.ts +5 -0
- package/dist/rules/lib/fresh.js +15 -0
- package/dist/rules/lib/options.d.ts +1 -0
- package/dist/rules/lib/options.js +5 -0
- package/dist/rules/lib/run-files.d.ts +18 -0
- package/dist/rules/lib/run-files.js +144 -0
- package/dist/rules/lib/token-layer.d.ts +19 -0
- package/dist/rules/lib/token-layer.js +170 -0
- package/dist/rules/lib/workspace-members.js +17 -19
- package/dist/rules/list-take-without-order.d.ts +24 -0
- package/dist/rules/list-take-without-order.js +293 -10
- package/dist/rules/no-orm-outside-db.js +1 -0
- package/dist/rules/subpath-over-barrel.d.ts +1 -0
- package/dist/rules/subpath-over-barrel.js +18 -12
- package/dist/rules/test-no-clock-polling-for-promise-flips.d.ts +5 -1
- package/dist/rules/test-no-clock-polling-for-promise-flips.js +3 -0
- package/dist/rules/tissue-must-compose.d.ts +1 -1
- package/dist/rules/tissue-no-data-props.d.ts +2 -2
- package/dist/rules/tokens-only-colors.d.ts +8 -11
- package/dist/rules/tokens-only-colors.js +36 -7
- package/dist/rules/worker-handles-are-scoped.d.ts +1 -1
- package/dist/timing.d.ts +18 -0
- package/dist/timing.js +75 -0
- package/package.json +2 -2
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// src/rules/subpath-over-barrel.ts
|
|
2
2
|
import { statSync } from "node:fs";
|
|
3
3
|
import { doorsOf, nodeModulesAbove, resolvePackage } from "./lib/doors.js";
|
|
4
|
+
import { onceFor } from "./lib/options.js";
|
|
4
5
|
import { bagOf } from "./lib/probe.js";
|
|
6
|
+
import { designatedFile } from "./lib/run-files.js";
|
|
5
7
|
import { optionsBag, STRING } from "./lib/schema.js";
|
|
6
8
|
import { hasWorkspaceAbove } from "./lib/workspace-members.js";
|
|
7
9
|
var homesOf = (declared) => new Map(Object.entries(declared.subpaths ?? {}).flatMap(([subpath, names]) => names.map((name) => [name, subpath])));
|
|
@@ -21,13 +23,6 @@ var doorsFor = (manifest) => {
|
|
|
21
23
|
return doors?.homes;
|
|
22
24
|
};
|
|
23
25
|
var PACKAGE_ROOT = /^(?:@[^/]+\/)?[^/]+$/;
|
|
24
|
-
var told = new Set;
|
|
25
|
-
var firstTime = (key) => {
|
|
26
|
-
if (told.has(key))
|
|
27
|
-
return false;
|
|
28
|
-
told.add(key);
|
|
29
|
-
return true;
|
|
30
|
-
};
|
|
31
26
|
var namesTakenBy = (node) => {
|
|
32
27
|
if (node.importKind === "type" || node.exportKind === "type")
|
|
33
28
|
return [];
|
|
@@ -48,14 +43,20 @@ var namesTakenBy = (node) => {
|
|
|
48
43
|
}
|
|
49
44
|
return names;
|
|
50
45
|
};
|
|
46
|
+
var nothingResolves = (file) => !nodeModulesAbove(file) && !hasWorkspaceAbove(file);
|
|
47
|
+
var ROOT_IMPORT = /\b(?:import|export)\s*\{\s*(?!type\s)[\w$][^}]*\}\s*from\s*['"](?:@[^/'"]+\/)?[^./'"@][^'"]*['"]/;
|
|
48
|
+
var toldWithoutCarrier = new Set;
|
|
51
49
|
var subpathOverBarrel = {
|
|
52
50
|
create(context) {
|
|
53
51
|
const declared = new Map((context.options?.[0]?.packages ?? []).filter((one) => typeof one.name === "string").map((one) => [one.name, one]));
|
|
54
52
|
const writtenByHand = [...declared.values()].some((one) => one.subpaths !== undefined);
|
|
53
|
+
const told = new Set;
|
|
54
|
+
const carrier = writtenByHand ? null : designatedFile("subpath-over-barrel", "nothingResolves", (file, text) => ROOT_IMPORT.test(text) && nothingResolves(file));
|
|
55
55
|
const tell = (messageId, source, node) => {
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
}
|
|
56
|
+
if (told.has(`${messageId}/${source}`))
|
|
57
|
+
return;
|
|
58
|
+
told.add(`${messageId}/${source}`);
|
|
59
|
+
context.report({ data: { package: source }, messageId, node });
|
|
59
60
|
};
|
|
60
61
|
const homesFor = (source, node) => {
|
|
61
62
|
const written = declared.get(source);
|
|
@@ -65,7 +66,8 @@ var subpathOverBarrel = {
|
|
|
65
66
|
if (found === undefined) {
|
|
66
67
|
if (written !== undefined)
|
|
67
68
|
tell("unresolvedPackage", source, node);
|
|
68
|
-
else if (!writtenByHand && PACKAGE_ROOT.test(source) &&
|
|
69
|
+
else if (carrier === null && !writtenByHand && PACKAGE_ROOT.test(source) && nothingResolves(context.filename) && !toldWithoutCarrier.has("nothingResolves") && onceFor("nothingResolves", context.filename)) {
|
|
70
|
+
toldWithoutCarrier.add("nothingResolves");
|
|
69
71
|
tell("nothingResolves", "", node);
|
|
70
72
|
}
|
|
71
73
|
return;
|
|
@@ -100,7 +102,11 @@ var subpathOverBarrel = {
|
|
|
100
102
|
};
|
|
101
103
|
return {
|
|
102
104
|
ExportNamedDeclaration: fromRoot,
|
|
103
|
-
ImportDeclaration: fromRoot
|
|
105
|
+
ImportDeclaration: fromRoot,
|
|
106
|
+
Program(node) {
|
|
107
|
+
if (carrier === context.filename)
|
|
108
|
+
tell("nothingResolves", "", node);
|
|
109
|
+
}
|
|
104
110
|
};
|
|
105
111
|
},
|
|
106
112
|
fixShape: `Import from the SUBPATH that owns the names, not from the package root:
|
|
@@ -2,7 +2,11 @@ import { type RuleContextWithRawOptions } from './lib/context.js';
|
|
|
2
2
|
import { type AnyNode } from './lib/nodes.js';
|
|
3
3
|
import { type Probe } from './lib/probe.js';
|
|
4
4
|
declare const testNoClockPollingForPromiseFlips: {
|
|
5
|
-
create(context: RuleContextWithRawOptions
|
|
5
|
+
create(context: RuleContextWithRawOptions & {
|
|
6
|
+
sourceCode?: {
|
|
7
|
+
text?: string;
|
|
8
|
+
};
|
|
9
|
+
}): {
|
|
6
10
|
ArrowFunctionExpression?: undefined;
|
|
7
11
|
FunctionDeclaration?: undefined;
|
|
8
12
|
FunctionExpression?: undefined;
|
|
@@ -76,6 +76,9 @@ var testNoClockPollingForPromiseFlips = {
|
|
|
76
76
|
const filename = normalise(context.filename);
|
|
77
77
|
if (!globs.some((pattern) => new RegExp(pattern).test(filename)))
|
|
78
78
|
return {};
|
|
79
|
+
const text = context.sourceCode?.text;
|
|
80
|
+
if (text !== undefined && !pollers.some((poller) => text.includes(poller)))
|
|
81
|
+
return {};
|
|
79
82
|
const inScope = (node) => {
|
|
80
83
|
const held = promisesIn(node);
|
|
81
84
|
if (held.size === 0)
|
|
@@ -4,9 +4,9 @@ import { type CompositionImportNode, type JsxOpeningElementNode } from './lib/re
|
|
|
4
4
|
declare const tissueMustCompose: {
|
|
5
5
|
create(context: RuleContext): {
|
|
6
6
|
ImportDeclaration?: undefined;
|
|
7
|
-
JSXOpeningElement?: undefined;
|
|
8
7
|
'Program:exit'?: undefined;
|
|
9
8
|
ExportNamedDeclaration?: undefined;
|
|
9
|
+
JSXOpeningElement?: undefined;
|
|
10
10
|
} | {
|
|
11
11
|
ExportNamedDeclaration(node: {
|
|
12
12
|
declaration?: {
|
|
@@ -48,12 +48,12 @@ interface DeclarationNode {
|
|
|
48
48
|
}
|
|
49
49
|
declare const tissueNoDataProps: {
|
|
50
50
|
create(context: RuleContext): {
|
|
51
|
-
'Program:exit'?: undefined;
|
|
52
51
|
ExportNamedDeclaration?: undefined;
|
|
53
52
|
ExportDefaultDeclaration?: undefined;
|
|
53
|
+
'Program:exit'?: undefined;
|
|
54
|
+
ImportDeclaration?: undefined;
|
|
54
55
|
TSInterfaceDeclaration?: undefined;
|
|
55
56
|
TSTypeAliasDeclaration?: undefined;
|
|
56
|
-
ImportDeclaration?: undefined;
|
|
57
57
|
} | {
|
|
58
58
|
ImportDeclaration(node: ModuleNode): void;
|
|
59
59
|
TSInterfaceDeclaration(node: DeclarationNode): void;
|
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
import { type RuleContextWithOptions } from './lib/context.js';
|
|
2
|
-
import type { AnyNode } from './lib/nodes.js';
|
|
3
2
|
import { type Probe } from './lib/probe.js';
|
|
4
|
-
import { type OpeningElement } from './lib/styling.js';
|
|
5
3
|
interface Options {
|
|
6
4
|
allow?: string[];
|
|
7
5
|
exemptModules?: string[];
|
|
6
|
+
ignoreKeys?: string[];
|
|
8
7
|
tokenPrefix?: string | string[];
|
|
9
8
|
}
|
|
10
9
|
declare const tokensOnlyColors: {
|
|
11
|
-
create(context: RuleContextWithOptions<Options>): {
|
|
12
|
-
JSXOpeningElement?: undefined;
|
|
13
|
-
Property?: undefined;
|
|
14
|
-
AssignmentPattern?: undefined;
|
|
15
|
-
} | {
|
|
16
|
-
JSXOpeningElement(node: OpeningElement): void;
|
|
17
|
-
Property(node: AnyNode): void;
|
|
18
|
-
AssignmentPattern(node: AnyNode): void;
|
|
19
|
-
};
|
|
10
|
+
create(context: RuleContextWithOptions<Options>): {};
|
|
20
11
|
fixShape: string;
|
|
21
12
|
meta: {
|
|
22
13
|
grants: {
|
|
@@ -45,6 +36,12 @@ declare const tokensOnlyColors: {
|
|
|
45
36
|
};
|
|
46
37
|
type: 'array';
|
|
47
38
|
};
|
|
39
|
+
ignoreKeys: {
|
|
40
|
+
items: {
|
|
41
|
+
type: 'string';
|
|
42
|
+
};
|
|
43
|
+
type: 'array';
|
|
44
|
+
};
|
|
48
45
|
tokenPrefix: {
|
|
49
46
|
anyOf: ({
|
|
50
47
|
items: {
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
styleObjectOf,
|
|
19
19
|
variableOf
|
|
20
20
|
} from "./lib/styling.js";
|
|
21
|
+
import { tokenLayerOf } from "./lib/token-layer.js";
|
|
21
22
|
var defaultedName = (node) => {
|
|
22
23
|
const left = node.left;
|
|
23
24
|
return left?.type === "Identifier" ? left.name ?? "default" : "default";
|
|
@@ -34,22 +35,32 @@ var tokenOffence = (value, prefixes, local) => {
|
|
|
34
35
|
};
|
|
35
36
|
var tokensOnlyColors = {
|
|
36
37
|
create(context) {
|
|
37
|
-
const { tokenPrefix, ...exempt } = context.options?.[0] ?? {};
|
|
38
|
+
const { ignoreKeys = [], tokenPrefix, ...exempt } = context.options?.[0] ?? {};
|
|
38
39
|
const prefixes = [requireOption("tokens-only-colors", tokenPrefix, "tokenPrefix")].flat();
|
|
39
40
|
const filename = normalise(context.filename);
|
|
40
41
|
if (isExempt(filename, exempt))
|
|
41
42
|
return {};
|
|
42
43
|
const readByAnElement = new WeakSet;
|
|
43
|
-
const
|
|
44
|
+
const tokenLayer = tokenLayerOf();
|
|
45
|
+
const pending = [];
|
|
46
|
+
const offenceIn = (property, value) => {
|
|
47
|
+
if (ignoreKeys.includes(property))
|
|
48
|
+
return;
|
|
44
49
|
const offence = tokenOffence(value, prefixes, new Set);
|
|
45
|
-
if (offence ===
|
|
50
|
+
if (offence === "colorLiteral" && isNamedOnly(value))
|
|
46
51
|
return;
|
|
47
52
|
if (offence === "foreignVariable" && !isColorProperty(property))
|
|
48
53
|
return;
|
|
49
|
-
|
|
54
|
+
return offence;
|
|
55
|
+
};
|
|
56
|
+
const reportDefault = (node, property, value) => {
|
|
57
|
+
const messageId = offenceIn(property, value);
|
|
58
|
+
if (messageId !== undefined)
|
|
59
|
+
context.report({ data: { property, value }, messageId, node });
|
|
50
60
|
};
|
|
51
61
|
const isTest = anyOf([...DEFAULT_TEST_EXEMPT, "__fixtures__/", "__visual__/"], filename);
|
|
52
62
|
return {
|
|
63
|
+
...tokenLayer.visitor,
|
|
53
64
|
JSXOpeningElement(node) {
|
|
54
65
|
const styled = styleObjectOf(node);
|
|
55
66
|
for (const entry of styled?.properties ?? []) {
|
|
@@ -91,7 +102,16 @@ var tokensOnlyColors = {
|
|
|
91
102
|
const name = keyOf(node);
|
|
92
103
|
if (name === undefined)
|
|
93
104
|
return;
|
|
94
|
-
|
|
105
|
+
if (offenceIn(name, value.value) === undefined)
|
|
106
|
+
return;
|
|
107
|
+
pending.push({ chain: tokenLayer.enclosing(), node, property: name, value: value.value });
|
|
108
|
+
},
|
|
109
|
+
"Program:exit"() {
|
|
110
|
+
tokenLayer.resolve();
|
|
111
|
+
for (const one of pending) {
|
|
112
|
+
if (!tokenLayer.covers(one.chain))
|
|
113
|
+
reportDefault(one.node, one.property, one.value);
|
|
114
|
+
}
|
|
95
115
|
},
|
|
96
116
|
AssignmentPattern(node) {
|
|
97
117
|
const right = node.right;
|
|
@@ -109,13 +129,21 @@ the brand switches and the component does not — add the value to the theme and
|
|
|
109
129
|
Configure \`tokenPrefix\` with the prefixes your themed properties use, and NAME the token layer's
|
|
110
130
|
modules in \`exemptModules\`, one per entry, because that is the one place a literal belongs —
|
|
111
131
|
\`allow\` still takes patterns and still grants its exemption to wherever the pattern stops, which is
|
|
112
|
-
a boundary nobody decided and nobody counts.
|
|
132
|
+
a boundary nobody decided and nobody counts. An object literal passed to \`defineTheme\`,
|
|
133
|
+
\`compile\`, \`toDtcg\`, \`emitVariables\` or \`valueAt\` from \`@geonosis/themekit\` — directly, or by a
|
|
134
|
+
single \`const\` in the same file — is the token layer itself and is not judged, and so is an object
|
|
135
|
+
literal typed as a themekit palette type (\`Theme\`, \`ThemeColors\`, \`ThemeMode\` …) imported from
|
|
136
|
+
\`@geonosis/themekit\`, by a \`const\` annotation, \`satisfies\` or \`as\`, which covers a theme
|
|
137
|
+
defined in its own module; a same-named local type does not count. Neither needs an
|
|
138
|
+
\`exemptModules\` entry. A key whose value looks like a colour but is data (a shader's mask channels,
|
|
139
|
+
a PDF spec) is named in \`ignoreKeys\`, one key per entry, and every object with that key is left
|
|
140
|
+
alone; the default is empty. Enabled with no \`tokenPrefix\` the rule
|
|
113
141
|
reports that once a run and judges nothing: it cannot tell a token from a foreign variable, and a rule that cannot tell reads
|
|
114
142
|
exactly like a clean tree.`,
|
|
115
143
|
meta: {
|
|
116
144
|
grants: { modules: "exemptModules", paths: "allow" },
|
|
117
145
|
docs: {
|
|
118
|
-
description: "Colours in a component come from the token layer. A colour literal in a className or an inline style fires, and so does a var() naming a custom property outside the configured token prefixes. Configure `tokenPrefix`; put the token layer in `allow`. Enabled without `tokenPrefix` it reports that once a run and judges nothing."
|
|
146
|
+
description: "Colours in a component come from the token layer. A colour literal in a className or an inline style fires, and so does a var() naming a custom property outside the configured token prefixes. Configure `tokenPrefix`; put the token layer in `allow`; name data keys that only look like colours in `ignoreKeys`. An object passed to a themekit function that takes a palette, or typed as a themekit palette type, is the token layer and is not judged. Enabled without `tokenPrefix` it reports that once a run and judges nothing."
|
|
119
147
|
},
|
|
120
148
|
messages: {
|
|
121
149
|
colorLiteral: 'Hardcoded colour "{{value}}" on `{{property}}`. Colours come from the token layer — use the themed variable or the utility that reads it, or the brand switches and this does not.',
|
|
@@ -124,6 +152,7 @@ exactly like a clean tree.`,
|
|
|
124
152
|
schema: optionsBag({
|
|
125
153
|
allow: STRINGS,
|
|
126
154
|
exemptModules: STRINGS,
|
|
155
|
+
ignoreKeys: STRINGS,
|
|
127
156
|
tokenPrefix: {
|
|
128
157
|
anyOf: [{ type: "string" }, STRINGS]
|
|
129
158
|
}
|
|
@@ -15,9 +15,9 @@ interface Callee {
|
|
|
15
15
|
}
|
|
16
16
|
declare const cellHandlesAreScoped: {
|
|
17
17
|
create(context: RuleContextWithOptions<Options>): {
|
|
18
|
-
ExportDefaultDeclaration?: undefined;
|
|
19
18
|
CallExpression?: undefined;
|
|
20
19
|
'Program:exit'?: undefined;
|
|
20
|
+
ExportDefaultDeclaration?: undefined;
|
|
21
21
|
ClassDeclaration?: undefined;
|
|
22
22
|
} | {
|
|
23
23
|
ExportDefaultDeclaration(node: {
|
package/dist/timing.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type Create<Context> = (context: Context) => Record<string, unknown>;
|
|
2
|
+
export type RuleTiming = {
|
|
3
|
+
createMs: number;
|
|
4
|
+
files: number;
|
|
5
|
+
rule: string;
|
|
6
|
+
totalMs: number;
|
|
7
|
+
visitMs: number;
|
|
8
|
+
};
|
|
9
|
+
export type TimingReport = {
|
|
10
|
+
files: number;
|
|
11
|
+
rules: RuleTiming[];
|
|
12
|
+
totalMs: number;
|
|
13
|
+
};
|
|
14
|
+
export declare const timingRequested: (env?: NodeJS.ProcessEnv) => boolean;
|
|
15
|
+
export declare const timingReport: () => TimingReport;
|
|
16
|
+
export declare const timingText: (report: TimingReport, top: number) => string;
|
|
17
|
+
export declare const withTiming: <Context>(create: Create<Context>, rule: string) => Create<Context>;
|
|
18
|
+
export {};
|
package/dist/timing.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// src/timing.ts
|
|
2
|
+
import { writeFileSync, writeSync } from "node:fs";
|
|
3
|
+
var DEFAULT_TOP = 10;
|
|
4
|
+
var spent = new Map;
|
|
5
|
+
var listening = false;
|
|
6
|
+
var timingRequested = (env = process.env) => {
|
|
7
|
+
const asked = env["GEONOSIS_LINT_TIMING"] ?? "";
|
|
8
|
+
return asked !== "" && asked !== "0" && asked !== "false";
|
|
9
|
+
};
|
|
10
|
+
var entryOf = (rule) => {
|
|
11
|
+
const known = spent.get(rule);
|
|
12
|
+
if (known !== undefined)
|
|
13
|
+
return known;
|
|
14
|
+
const created = { createMs: 0, files: 0, visitMs: 0 };
|
|
15
|
+
spent.set(rule, created);
|
|
16
|
+
return created;
|
|
17
|
+
};
|
|
18
|
+
var timingReport = () => {
|
|
19
|
+
const rules = [...spent.entries()].map(([rule, one]) => ({ ...one, rule, totalMs: one.createMs + one.visitMs })).toSorted((a, b) => b.totalMs - a.totalMs);
|
|
20
|
+
return {
|
|
21
|
+
files: rules.reduce((most, one) => Math.max(most, one.files), 0),
|
|
22
|
+
rules,
|
|
23
|
+
totalMs: rules.reduce((sum, one) => sum + one.totalMs, 0)
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
var ms = (value) => `${value.toFixed(1)} ms`;
|
|
27
|
+
var timingText = (report, top) => {
|
|
28
|
+
const shown = report.rules.slice(0, top);
|
|
29
|
+
const width = Math.max(0, ...shown.map((one) => one.rule.length));
|
|
30
|
+
const create = report.rules.reduce((sum, one) => sum + one.createMs, 0);
|
|
31
|
+
return [
|
|
32
|
+
`geonosis-lint-timing: ${report.rules.length} rule(s) took ${ms(report.totalMs)} in this plugin over ${report.files} file(s) (create ${ms(create)}, visit ${ms(report.totalMs - create)}); the slowest ${shown.length}:`,
|
|
33
|
+
...shown.map((one) => ` ${one.rule.padEnd(width)} ${ms(one.totalMs).padStart(10)} (create ${ms(one.createMs)}, visit ${ms(one.visitMs)}, ${one.files} files)`),
|
|
34
|
+
""
|
|
35
|
+
].join(`
|
|
36
|
+
`);
|
|
37
|
+
};
|
|
38
|
+
var flush = () => {
|
|
39
|
+
const report = timingReport();
|
|
40
|
+
const top = Number(process.env["GEONOSIS_LINT_TIMING_TOP"]);
|
|
41
|
+
writeSync(2, timingText(report, Number.isInteger(top) && top > 0 ? top : DEFAULT_TOP));
|
|
42
|
+
const into = process.env["GEONOSIS_LINT_TIMING_FILE"];
|
|
43
|
+
if (into !== undefined && into !== "") {
|
|
44
|
+
writeFileSync(into, `${JSON.stringify(report, null, 2)}
|
|
45
|
+
`);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var withTiming = (create, rule) => (context) => {
|
|
49
|
+
if (!listening) {
|
|
50
|
+
listening = true;
|
|
51
|
+
process.on("exit", flush);
|
|
52
|
+
}
|
|
53
|
+
const entry = entryOf(rule);
|
|
54
|
+
const began = performance.now();
|
|
55
|
+
const visitor = create(context);
|
|
56
|
+
entry.createMs += performance.now() - began;
|
|
57
|
+
entry.files += 1;
|
|
58
|
+
return Object.fromEntries(Object.entries(visitor).map(([name, handler]) => [
|
|
59
|
+
name,
|
|
60
|
+
typeof handler === "function" ? (node) => {
|
|
61
|
+
const started = performance.now();
|
|
62
|
+
try {
|
|
63
|
+
return handler(node);
|
|
64
|
+
} finally {
|
|
65
|
+
entry.visitMs += performance.now() - started;
|
|
66
|
+
}
|
|
67
|
+
} : handler
|
|
68
|
+
]));
|
|
69
|
+
};
|
|
70
|
+
export {
|
|
71
|
+
timingReport,
|
|
72
|
+
timingRequested,
|
|
73
|
+
timingText,
|
|
74
|
+
withTiming
|
|
75
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geonosis/oxlint-plugin-biological-architecture",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"types": "./dist/index.d.ts",
|
|
5
5
|
"description": "Biological tier architecture as lint — the union of a Medusa storefront's and a Workers + D1 app's rule sets, shipped as presets.",
|
|
6
6
|
"keywords": [
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"oxlint": ">=1.77"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@geonosis/lint-parity": "3.
|
|
48
|
+
"@geonosis/lint-parity": "3.3.0"
|
|
49
49
|
},
|
|
50
50
|
"engines": {
|
|
51
51
|
"node": ">=22"
|