@geonosis/oxlint-plugin-biological-architecture 2.5.0 → 2.5.1
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 +2 -0
- package/dist/index.js +83 -131
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -196,6 +196,33 @@ var walkNode = (node, visit) => {
|
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
};
|
|
199
|
+
var FUNCTION_BOUNDARY_TYPES = /* @__PURE__ */ new Set([
|
|
200
|
+
"ArrowFunctionExpression",
|
|
201
|
+
"FunctionDeclaration",
|
|
202
|
+
"FunctionExpression"
|
|
203
|
+
]);
|
|
204
|
+
var SKIPPED_KEYS = /* @__PURE__ */ new Set(["loc", "parent", "range", "scope"]);
|
|
205
|
+
var walkOwnFunctionBody = (body, visit) => {
|
|
206
|
+
const visited = /* @__PURE__ */ new WeakSet();
|
|
207
|
+
const walk = (node) => {
|
|
208
|
+
if (node === null || typeof node !== "object" || visited.has(node)) return;
|
|
209
|
+
visited.add(node);
|
|
210
|
+
const record2 = node;
|
|
211
|
+
if (node !== body && typeof record2.type === "string" && FUNCTION_BOUNDARY_TYPES.has(record2.type)) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (typeof record2.type === "string") visit(record2);
|
|
215
|
+
for (const [key, value] of Object.entries(record2)) {
|
|
216
|
+
if (SKIPPED_KEYS.has(key) || value === null || typeof value !== "object") continue;
|
|
217
|
+
if (Array.isArray(value)) {
|
|
218
|
+
for (const item of value) walk(item);
|
|
219
|
+
} else {
|
|
220
|
+
walk(value);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
walk(body);
|
|
225
|
+
};
|
|
199
226
|
var propertyNameOf = (node) => {
|
|
200
227
|
if (node.type !== "MemberExpression" || node.computed === true) return null;
|
|
201
228
|
const property = node.property;
|
|
@@ -423,7 +450,22 @@ keeps inside its own body is its own business.`,
|
|
|
423
450
|
var boundary_time_is_primitive_default = boundaryTimeIsPrimitive;
|
|
424
451
|
|
|
425
452
|
// src/rules/cell-must-be-stateful.ts
|
|
426
|
-
import * as
|
|
453
|
+
import * as fs2 from "fs";
|
|
454
|
+
|
|
455
|
+
// src/rules/lib/reexports.ts
|
|
456
|
+
import fs from "fs";
|
|
457
|
+
function isReexportOnly(file) {
|
|
458
|
+
try {
|
|
459
|
+
const src = fs.readFileSync(file, "utf8").replaceAll(/\/\*[\s\S]*?\*\//g, "").replaceAll(/\/\/.*$/gm, "");
|
|
460
|
+
const hasReexport = /\bexport\b[^;]*\bfrom\b\s*['"]/.test(src);
|
|
461
|
+
const hasComponent = /\bfunction\b|=>|\breturn\b|<[A-Za-z]/.test(src);
|
|
462
|
+
return hasReexport && !hasComponent;
|
|
463
|
+
} catch {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// src/rules/cell-must-be-stateful.ts
|
|
427
469
|
var STATE_SIGNATURE = /\buse[A-Z]\w*\s*\(|from\s+['"][^'"]*\/organelles(?:\/|['"])|from\s+['"][^'"]*\/stores?(?:\/|['"])|createContext\s*\(/;
|
|
428
470
|
var SIBLING_SUFFIXES = [
|
|
429
471
|
".tsx",
|
|
@@ -438,22 +480,12 @@ var SIBLING_SUFFIXES = [
|
|
|
438
480
|
".steps.tsx"
|
|
439
481
|
];
|
|
440
482
|
var PRIVATE_OR_NON_ENTRY = /\.(parts|sections|layouts|types|icons|sidebar|summary|form|data|steps|stories|test|spec)\.tsx$/;
|
|
441
|
-
function isReexportOnly(file) {
|
|
442
|
-
try {
|
|
443
|
-
const src = fs.readFileSync(file, "utf8").replaceAll(/\/\*[\s\S]*?\*\//g, "").replaceAll(/\/\/.*$/gm, "");
|
|
444
|
-
const hasReexport = /\bexport\b[^;]*\bfrom\b\s*['"]/.test(src);
|
|
445
|
-
const hasComponent = /\bfunction\b|=>|\breturn\b|<[A-Za-z]/.test(src);
|
|
446
|
-
return hasReexport && !hasComponent;
|
|
447
|
-
} catch {
|
|
448
|
-
return false;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
483
|
function familyHasState(mainFile) {
|
|
452
484
|
const stem = mainFile.replace(/\.tsx$/, "");
|
|
453
485
|
for (const suffix of SIBLING_SUFFIXES) {
|
|
454
486
|
const file = stem + suffix;
|
|
455
487
|
try {
|
|
456
|
-
if (
|
|
488
|
+
if (fs2.existsSync(file) && STATE_SIGNATURE.test(fs2.readFileSync(file, "utf8"))) {
|
|
457
489
|
return true;
|
|
458
490
|
}
|
|
459
491
|
} catch {
|
|
@@ -1832,18 +1864,24 @@ Composing a stateful primitive makes the file a cell. \`export \u2026 from\` is
|
|
|
1832
1864
|
});
|
|
1833
1865
|
var molecule_atoms_only_default = moleculeAtomsOnly;
|
|
1834
1866
|
|
|
1835
|
-
// src/rules/
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1867
|
+
// src/rules/lib/render-tracking.ts
|
|
1868
|
+
var rootTagNameOf = (name) => {
|
|
1869
|
+
if (!name) return void 0;
|
|
1870
|
+
if (name.type === "JSXIdentifier") return name.name;
|
|
1871
|
+
if (name.type === "JSXMemberExpression") return name.object?.name;
|
|
1872
|
+
return void 0;
|
|
1873
|
+
};
|
|
1874
|
+
var collectCompositionLocals = (node, isComposition, into) => {
|
|
1875
|
+
if (node.importKind === "type") return;
|
|
1876
|
+
if (!isComposition(node.source.value)) return;
|
|
1877
|
+
for (const spec of node.specifiers ?? []) {
|
|
1878
|
+
const localName = spec.local?.name;
|
|
1879
|
+
if (localName) into.add(localName);
|
|
1845
1880
|
}
|
|
1846
|
-
}
|
|
1881
|
+
};
|
|
1882
|
+
var rendersAnyOf = (locals, renderedTagNames) => [...locals].some((local) => renderedTagNames.has(local));
|
|
1883
|
+
|
|
1884
|
+
// src/rules/molecule-must-compose.ts
|
|
1847
1885
|
function isCompositionSource(src) {
|
|
1848
1886
|
if (/\/(atoms|molecules)(\/|$)/.test(src)) return true;
|
|
1849
1887
|
if (/^\.\/[^/]+$/.test(src)) return true;
|
|
@@ -1856,38 +1894,21 @@ var moleculeMustCompose = {
|
|
|
1856
1894
|
const filename = context.filename;
|
|
1857
1895
|
const isMolecule = /\/molecules\/(?!index\.)[^/]+\.tsx?$/.test(filename);
|
|
1858
1896
|
if (!isMolecule) return {};
|
|
1859
|
-
if (
|
|
1897
|
+
if (isReexportOnly(filename)) return {};
|
|
1860
1898
|
const bioImportLocals = /* @__PURE__ */ new Set();
|
|
1861
1899
|
const renderedTagNames = /* @__PURE__ */ new Set();
|
|
1862
1900
|
let programNode = null;
|
|
1863
1901
|
return {
|
|
1864
1902
|
ImportDeclaration(node) {
|
|
1865
|
-
|
|
1866
|
-
const source = node.source.value;
|
|
1867
|
-
if (!isCompositionSource(source)) return;
|
|
1868
|
-
for (const spec of node.specifiers ?? []) {
|
|
1869
|
-
const localName = spec.local?.name;
|
|
1870
|
-
if (localName) bioImportLocals.add(localName);
|
|
1871
|
-
}
|
|
1903
|
+
collectCompositionLocals(node, isCompositionSource, bioImportLocals);
|
|
1872
1904
|
},
|
|
1873
1905
|
JSXOpeningElement(node) {
|
|
1874
|
-
const
|
|
1875
|
-
if (
|
|
1876
|
-
if (name.type === "JSXIdentifier") {
|
|
1877
|
-
const id = name;
|
|
1878
|
-
if (id.name) renderedTagNames.add(id.name);
|
|
1879
|
-
} else if (name.type === "JSXMemberExpression") {
|
|
1880
|
-
const member = name;
|
|
1881
|
-
const root = member.object?.name;
|
|
1882
|
-
if (root) renderedTagNames.add(root);
|
|
1883
|
-
}
|
|
1906
|
+
const root = rootTagNameOf(node.name);
|
|
1907
|
+
if (root) renderedTagNames.add(root);
|
|
1884
1908
|
},
|
|
1885
1909
|
"Program:exit"(node) {
|
|
1886
1910
|
programNode = node;
|
|
1887
|
-
|
|
1888
|
-
(localName) => renderedTagNames.has(localName)
|
|
1889
|
-
);
|
|
1890
|
-
if (!composesByRender) {
|
|
1911
|
+
if (!rendersAnyOf(bioImportLocals, renderedTagNames)) {
|
|
1891
1912
|
const shortName = filename.split("/").pop() || filename;
|
|
1892
1913
|
context.report({
|
|
1893
1914
|
data: { filename: shortName },
|
|
@@ -2089,32 +2110,11 @@ var FALLBACK_BASENAMES = /* @__PURE__ */ new Set([
|
|
|
2089
2110
|
"loading.tsx",
|
|
2090
2111
|
"not-found.tsx"
|
|
2091
2112
|
]);
|
|
2092
|
-
var SKIP_KEYS = /* @__PURE__ */ new Set(["parent", "loc", "range", "scope"]);
|
|
2093
2113
|
function collectReturnStatements(body) {
|
|
2094
2114
|
const out = [];
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
if (visited.has(node)) return;
|
|
2099
|
-
visited.add(node);
|
|
2100
|
-
const n = node;
|
|
2101
|
-
if (node !== body && (n.type === "FunctionDeclaration" || n.type === "FunctionExpression" || n.type === "ArrowFunctionExpression")) {
|
|
2102
|
-
return;
|
|
2103
|
-
}
|
|
2104
|
-
if (n.type === "ReturnStatement") {
|
|
2105
|
-
out.push(node);
|
|
2106
|
-
}
|
|
2107
|
-
for (const key of Object.keys(n)) {
|
|
2108
|
-
if (SKIP_KEYS.has(key)) continue;
|
|
2109
|
-
const v = n[key];
|
|
2110
|
-
if (Array.isArray(v)) {
|
|
2111
|
-
for (const item of v) walk(item);
|
|
2112
|
-
} else if (v && typeof v === "object") {
|
|
2113
|
-
walk(v);
|
|
2114
|
-
}
|
|
2115
|
-
}
|
|
2116
|
-
}
|
|
2117
|
-
walk(body);
|
|
2115
|
+
walkOwnFunctionBody(body, (node) => {
|
|
2116
|
+
if (node.type === "ReturnStatement") out.push(node);
|
|
2117
|
+
});
|
|
2118
2118
|
return out;
|
|
2119
2119
|
}
|
|
2120
2120
|
function tagOf(el) {
|
|
@@ -3978,37 +3978,13 @@ var ROUTE_BASENAMES = /* @__PURE__ */ new Set([
|
|
|
3978
3978
|
"template.tsx"
|
|
3979
3979
|
]);
|
|
3980
3980
|
var R3F_PACKAGES2 = /* @__PURE__ */ new Set(["@react-three/drei", "@react-three/fiber"]);
|
|
3981
|
-
var WALK_SKIP_KEYS = /* @__PURE__ */ new Set(["parent", "loc", "range", "scope"]);
|
|
3982
3981
|
function functionHasHookCall(body) {
|
|
3983
3982
|
let found = false;
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
if (
|
|
3988
|
-
|
|
3989
|
-
visited.add(node);
|
|
3990
|
-
const n = node;
|
|
3991
|
-
if (n.type === "FunctionDeclaration" || n.type === "FunctionExpression" || n.type === "ArrowFunctionExpression") {
|
|
3992
|
-
if (node !== body) return;
|
|
3993
|
-
}
|
|
3994
|
-
if (n.type === "CallExpression") {
|
|
3995
|
-
const callee = n.callee;
|
|
3996
|
-
if (callee?.type === "Identifier" && callee.name && /^use[A-Z]/.test(callee.name)) {
|
|
3997
|
-
found = true;
|
|
3998
|
-
return;
|
|
3999
|
-
}
|
|
4000
|
-
}
|
|
4001
|
-
for (const key of Object.keys(n)) {
|
|
4002
|
-
if (WALK_SKIP_KEYS.has(key)) continue;
|
|
4003
|
-
const v = n[key];
|
|
4004
|
-
if (Array.isArray(v)) {
|
|
4005
|
-
for (const item of v) walk(item);
|
|
4006
|
-
} else if (v && typeof v === "object") {
|
|
4007
|
-
walk(v);
|
|
4008
|
-
}
|
|
4009
|
-
}
|
|
4010
|
-
}
|
|
4011
|
-
walk(body);
|
|
3983
|
+
walkOwnFunctionBody(body, (node) => {
|
|
3984
|
+
if (found || node.type !== "CallExpression") return;
|
|
3985
|
+
const callee = node.callee;
|
|
3986
|
+
if (callee?.type === "Identifier" && callee.name && /^use[A-Z]/.test(callee.name)) found = true;
|
|
3987
|
+
});
|
|
4012
3988
|
return found;
|
|
4013
3989
|
}
|
|
4014
3990
|
function hasMeaningfulChildren(el) {
|
|
@@ -6240,7 +6216,6 @@ export const clock = (): unknown => theClock
|
|
|
6240
6216
|
var time_through_the_door_default = timeThroughTheDoor;
|
|
6241
6217
|
|
|
6242
6218
|
// src/rules/tissue-must-compose.ts
|
|
6243
|
-
import fs4 from "fs";
|
|
6244
6219
|
var COMPOSITION_PATTERNS = [
|
|
6245
6220
|
// Tier segment with a trailing slash OR at the end of the specifier, so both
|
|
6246
6221
|
// relative (`../cells/x`, `../../feature/cells/x`) and self-subpath feature
|
|
@@ -6254,16 +6229,6 @@ var COMPOSITION_PATTERNS = [
|
|
|
6254
6229
|
// real composition, not lint-theater.
|
|
6255
6230
|
/\.(parts|sections|layouts)$/
|
|
6256
6231
|
];
|
|
6257
|
-
function isReexportOnly3(file) {
|
|
6258
|
-
try {
|
|
6259
|
-
const src = fs4.readFileSync(file, "utf8").replaceAll(/\/\*[\s\S]*?\*\//g, "").replaceAll(/\/\/.*$/gm, "");
|
|
6260
|
-
const hasReexport = /\bexport\b[^;]*\bfrom\b\s*['"]/.test(src);
|
|
6261
|
-
const hasComponent = /\bfunction\b|=>|\breturn\b|<[A-Za-z]/.test(src);
|
|
6262
|
-
return hasReexport && !hasComponent;
|
|
6263
|
-
} catch {
|
|
6264
|
-
return false;
|
|
6265
|
-
}
|
|
6266
|
-
}
|
|
6267
6232
|
var APP_ROUTE_MUST_COMPOSE = /* @__PURE__ */ new Set(["page.tsx", "layout.tsx", "template.tsx"]);
|
|
6268
6233
|
var APP_ROUTE_FALLBACKS = /* @__PURE__ */ new Set([
|
|
6269
6234
|
"default.tsx",
|
|
@@ -6292,7 +6257,7 @@ var tissueMustCompose = {
|
|
|
6292
6257
|
const isAppFallback = /\/app\//.test(normalized) && APP_ROUTE_FALLBACKS.has(basename);
|
|
6293
6258
|
if (isAppFallback) return {};
|
|
6294
6259
|
if (!isTissue && !isAppRouteFile) return {};
|
|
6295
|
-
if (
|
|
6260
|
+
if (isReexportOnly(filename)) return {};
|
|
6296
6261
|
const bioImportLocals = /* @__PURE__ */ new Set();
|
|
6297
6262
|
const renderedTagNames = /* @__PURE__ */ new Set();
|
|
6298
6263
|
let hasMetadataExport = false;
|
|
@@ -6316,25 +6281,15 @@ var tissueMustCompose = {
|
|
|
6316
6281
|
}
|
|
6317
6282
|
},
|
|
6318
6283
|
ImportDeclaration(node) {
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
if (localName) bioImportLocals.add(localName);
|
|
6325
|
-
}
|
|
6284
|
+
collectCompositionLocals(
|
|
6285
|
+
node,
|
|
6286
|
+
(source) => COMPOSITION_PATTERNS.some((pattern) => pattern.test(source)),
|
|
6287
|
+
bioImportLocals
|
|
6288
|
+
);
|
|
6326
6289
|
},
|
|
6327
6290
|
JSXOpeningElement(node) {
|
|
6328
|
-
const
|
|
6329
|
-
if (
|
|
6330
|
-
if (name.type === "JSXIdentifier") {
|
|
6331
|
-
const id = name;
|
|
6332
|
-
if (id.name) renderedTagNames.add(id.name);
|
|
6333
|
-
} else if (name.type === "JSXMemberExpression") {
|
|
6334
|
-
const member = name;
|
|
6335
|
-
const root = member.object?.name;
|
|
6336
|
-
if (root) renderedTagNames.add(root);
|
|
6337
|
-
}
|
|
6291
|
+
const root = rootTagNameOf(node.name);
|
|
6292
|
+
if (root) renderedTagNames.add(root);
|
|
6338
6293
|
},
|
|
6339
6294
|
"Program:exit"(node) {
|
|
6340
6295
|
programNode = node;
|
|
@@ -6345,10 +6300,7 @@ var tissueMustCompose = {
|
|
|
6345
6300
|
if (basename === "page.tsx" && isTrulyEmpty) {
|
|
6346
6301
|
return;
|
|
6347
6302
|
}
|
|
6348
|
-
|
|
6349
|
-
(localName) => renderedTagNames.has(localName)
|
|
6350
|
-
);
|
|
6351
|
-
if (!composesByRender) {
|
|
6303
|
+
if (!rendersAnyOf(bioImportLocals, renderedTagNames)) {
|
|
6352
6304
|
const shortName = normalized.split("/").pop() || normalized;
|
|
6353
6305
|
const reason = bioImportLocals.size === 0 ? "noBioImports" : "bioImportedButNotRendered";
|
|
6354
6306
|
context.report({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geonosis/oxlint-plugin-biological-architecture",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.1",
|
|
4
4
|
"types": "./dist/index.d.ts",
|
|
5
5
|
"description": "Biological tier architecture as lint — the union of the dielime and during.day 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": "2.5.
|
|
48
|
+
"@geonosis/lint-parity": "2.5.1"
|
|
49
49
|
},
|
|
50
50
|
"engines": {
|
|
51
51
|
"node": ">=22"
|