@geonosis/oxlint-plugin-biological-architecture 1.1.0 → 1.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/corpus/.oxlintrc.json +4 -1
- package/corpus/emits/member-call-budget.ts +8 -0
- package/corpus/integrations/providers/warp/index.ts +1 -0
- package/corpus/integrations/registry.fulfillment.ts +4 -0
- package/corpus/integrations/registry.ts +6 -3
- package/dist/index.d.ts +14 -1
- package/dist/index.js +52 -24
- package/package.json +2 -2
package/corpus/.oxlintrc.json
CHANGED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// A budget built by a call the rule cannot name: reported, never a silent pass.
|
|
2
|
+
declare const emitEventStep: (input: unknown) => unknown
|
|
3
|
+
declare const retry: { background: () => unknown }
|
|
4
|
+
|
|
5
|
+
export const emitted = emitEventStep({
|
|
6
|
+
eventName: 'sample.converted',
|
|
7
|
+
options: retry.background(),
|
|
8
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const WarpProvider = { id: 'cp_warp' }
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// The other half of a SPLIT registry: the file beside it imports this one, and `warp` is
|
|
2
|
+
// registered only here. Reading the named half alone reported two registered carrier directories
|
|
3
|
+
// as dead weight on a real consumer, which is how a rule gets turned off.
|
|
4
|
+
export const carriers = ['providers/warp']
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { AttioProvider } from './providers/attio/index'
|
|
2
|
+
import { carriers } from './registry.fulfillment'
|
|
2
3
|
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// directories — Midday carries
|
|
4
|
+
// This registry is SPLIT: two of the four directories beside it are named here, `warp` is named in
|
|
5
|
+
// the half this file imports, and `dead-cal` is named in neither. `dead-cal` ships a manifest, is
|
|
6
|
+
// imported by nothing, and reads as an integration to anyone counting directories — Midday carries
|
|
7
|
+
// eight of these.
|
|
6
8
|
export const providers = {
|
|
9
|
+
carriers,
|
|
7
10
|
cp_attio: AttioProvider,
|
|
8
11
|
cp_hubspot: 'providers/hubspot',
|
|
9
12
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -21,7 +21,20 @@ type Probe = {
|
|
|
21
21
|
path: string;
|
|
22
22
|
source: string;
|
|
23
23
|
};
|
|
24
|
-
|
|
24
|
+
/**
|
|
25
|
+
* A probe that needs a TREE rather than a file.
|
|
26
|
+
*
|
|
27
|
+
* A rule that reads a sibling off disk — a registry, a schema's twin — cannot be exercised by one
|
|
28
|
+
* file however right its source is: `no-unregistered-plugin-dir` returns silently when the registry
|
|
29
|
+
* its options name is not there, so the single-file probe fired nothing and every consumer
|
|
30
|
+
* enforcing the rule over a real registry read as a permanent FAIL. The answer is not a widened
|
|
31
|
+
* probe, which would be the rule claiming evidence it does not have; it is a probe that may declare
|
|
32
|
+
* the sibling too, and a reader that mounts all of them.
|
|
33
|
+
*/
|
|
34
|
+
type ProbeTree = {
|
|
35
|
+
files: readonly Probe[];
|
|
36
|
+
};
|
|
37
|
+
type ProbeFn = (options: readonly unknown[]) => Probe | ProbeTree;
|
|
25
38
|
|
|
26
39
|
type Rule = {
|
|
27
40
|
create: (context: never) => Record<string, unknown>;
|
package/dist/index.js
CHANGED
|
@@ -593,10 +593,10 @@ var dialect_through_the_seam_default = dialectThroughTheSeam;
|
|
|
593
593
|
// src/rules/lib/options.ts
|
|
594
594
|
var NOTHING = "nothing";
|
|
595
595
|
var isMissing = (value) => value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
|
|
596
|
-
var requireOption = (rule, value, option) => {
|
|
596
|
+
var requireOption = (rule, value, option, accepts) => {
|
|
597
597
|
if (isMissing(value)) {
|
|
598
598
|
throw new Error(
|
|
599
|
-
`biological-architecture/${rule} is enabled but its \`${option}\` option is ${NOTHING}, so it can never fire. Configure \`${option}\`, or turn the rule off \u2014 an enabled rule that cannot fire reads exactly like a rule that found nothing.`
|
|
599
|
+
`biological-architecture/${rule} is enabled but its \`${option}\` option is ${NOTHING}, so it can never fire. Configure \`${option}\`, or turn the rule off \u2014 an enabled rule that cannot fire reads exactly like a rule that found nothing.${accepts === void 0 ? "" : ` It accepts ${accepts}.`}`
|
|
600
600
|
);
|
|
601
601
|
}
|
|
602
602
|
return value;
|
|
@@ -854,9 +854,19 @@ var emitDeclaresAttempts = {
|
|
|
854
854
|
const site = named?.type === "Literal" && typeof named.value === "string" ? named.value : emitter;
|
|
855
855
|
const options = valueOf(properties, OPTIONS);
|
|
856
856
|
if (options !== void 0 && objectOf(options) === null) {
|
|
857
|
-
if (budgetHelpers.size === 0)
|
|
857
|
+
if (budgetHelpers.size === 0) {
|
|
858
|
+
context.report({ data: { emitter, site }, messageId: "undeclaredHelpers", node });
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
858
861
|
const call = options;
|
|
859
|
-
if (call.type !== "CallExpression" || call.callee?.type !== "Identifier")
|
|
862
|
+
if (call.type !== "CallExpression" || call.callee?.type !== "Identifier") {
|
|
863
|
+
context.report({
|
|
864
|
+
data: { emitter, helper: "a call this rule cannot name", site },
|
|
865
|
+
messageId: "opaqueBudget",
|
|
866
|
+
node
|
|
867
|
+
});
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
860
870
|
if (budgetHelpers.has(call.callee.name ?? "")) return;
|
|
861
871
|
context.report({
|
|
862
872
|
data: { emitter, helper: call.callee.name ?? "a call", site },
|
|
@@ -886,8 +896,9 @@ var emitDeclaresAttempts = {
|
|
|
886
896
|
than written as a number at the call site. \`attempts\` defaults to 1 and the worker reads only
|
|
887
897
|
\`attempts > 1\` as "a retry was configured", so an emit with \`{ priority }\` and no \`attempts\` is
|
|
888
898
|
droppable while looking deliberate. Pass \`options: { attempts: RETRY_BACKGROUND.attempts, priority }\`.
|
|
889
|
-
A budget the rule cannot evaluate \u2014 a constant
|
|
890
|
-
|
|
899
|
+
A literal budget the rule cannot evaluate \u2014 a shared constant's property \u2014 passes; an options bag
|
|
900
|
+
built by a CALL never passes silently: with no \`budgetHelpers\` declared it is reported as such, and
|
|
901
|
+
with them declared any call that is not one of them is reported. Name the repo's own budget builders in \`budgetHelpers\`
|
|
891
902
|
(dielime's is \`retryableEvent\`, which throws on \`attempts <= 1\`) and a call to one of them counts as
|
|
892
903
|
a budget while a call to anything else is reported: without that, a repo whose every call site goes
|
|
893
904
|
through a helper gets zero findings and no coverage. Configure \`emitters\` if the repo wraps the emit
|
|
@@ -898,6 +909,7 @@ under another name.`,
|
|
|
898
909
|
},
|
|
899
910
|
messages: {
|
|
900
911
|
noBudget: '`{{emitter}}` emits "{{site}}" with no `options.attempts`. `attempts` defaults to 1 and the worker treats only `attempts > 1` as a configured retry, so this event is dropped on its first failure. Give it a budget from the shared retry constants.',
|
|
912
|
+
undeclaredHelpers: '`{{emitter}}` emits "{{site}}" with a budget built by a call, and this repo declares no `budgetHelpers` \u2014 nothing here can tell whether that call configures a retry. Name the repo\'s budget builders in `budgetHelpers`, or pass `attempts` where it can be read.',
|
|
901
913
|
opaqueBudget: '`{{emitter}}` emits "{{site}}" with a budget built by `{{helper}}`, which this repo has not named as a budget helper. The bus reads only `attempts > 1` as a configured retry, and nothing here can tell whether that call produces one. Use one of the declared `budgetHelpers`, or pass `attempts` where it can be read.',
|
|
902
914
|
tooFew: '`{{emitter}}` emits "{{site}}" with `attempts: {{attempts}}`, which is the default spelled out \u2014 one attempt is no retry. Take the budget from the shared retry constants instead.'
|
|
903
915
|
},
|
|
@@ -3409,6 +3421,7 @@ var no_type_definitions_in_components_default = noTypeDefinitionsInComponents;
|
|
|
3409
3421
|
// src/rules/no-unregistered-plugin-dir.ts
|
|
3410
3422
|
import { readFileSync as readFileSync3 } from "fs";
|
|
3411
3423
|
var RULE8 = "no-unregistered-plugin-dir";
|
|
3424
|
+
var ACCEPTS = "{ roots: string[], registry: string | string[], manifests: string[] }";
|
|
3412
3425
|
var trimSlashes = (path2) => path2.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
3413
3426
|
var withoutComments = (source) => source.replaceAll(/\/\*[\s\S]*?\*\//g, " ").replaceAll(/\/\/[^\n]*/g, " ");
|
|
3414
3427
|
var literalsOf = (source) => [...withoutComments(source).matchAll(/['"`]([^'"`\n]+)['"`]/g)].map((match) => match[1] ?? "");
|
|
@@ -3425,9 +3438,10 @@ var rootedAt = (filename, root) => {
|
|
|
3425
3438
|
var noUnregisteredPluginDir = {
|
|
3426
3439
|
create(context) {
|
|
3427
3440
|
const options = context.options?.[0] ?? {};
|
|
3428
|
-
const roots = requireOption(RULE8, options.roots, "roots").map(trimSlashes);
|
|
3429
|
-
const
|
|
3430
|
-
const
|
|
3441
|
+
const roots = requireOption(RULE8, options.roots, "roots", ACCEPTS).map(trimSlashes);
|
|
3442
|
+
const declared = requireOption(RULE8, options.registry, "registry", ACCEPTS);
|
|
3443
|
+
const registries = (typeof declared === "string" ? [declared] : declared).map(trimSlashes);
|
|
3444
|
+
const manifests = new Set(requireOption(RULE8, options.manifests, "manifests", ACCEPTS));
|
|
3431
3445
|
const filename = normalise(context.filename);
|
|
3432
3446
|
return {
|
|
3433
3447
|
Program(node) {
|
|
@@ -3440,11 +3454,12 @@ var noUnregisteredPluginDir = {
|
|
|
3440
3454
|
if (directory === void 0 || file === void 0 || !manifests.has(file)) continue;
|
|
3441
3455
|
let source = "";
|
|
3442
3456
|
try {
|
|
3443
|
-
source = readFileSync3(`${rooted.repoRoot}/${
|
|
3457
|
+
source = registries.map((half) => readFileSync3(`${rooted.repoRoot}/${half}`, "utf8")).join("\n");
|
|
3444
3458
|
} catch {
|
|
3445
3459
|
return;
|
|
3446
3460
|
}
|
|
3447
3461
|
if (literalsOf(source).some((literal) => names(literal, directory))) return;
|
|
3462
|
+
const registry = registries.join(", ");
|
|
3448
3463
|
context.report({ data: { directory, registry, root }, messageId: "unregistered", node });
|
|
3449
3464
|
return;
|
|
3450
3465
|
}
|
|
@@ -3454,13 +3469,14 @@ var noUnregisteredPluginDir = {
|
|
|
3454
3469
|
fixShape: `Every directory under a plugin root is named by the registry. Add the directory to the registry file \u2014
|
|
3455
3470
|
as an import path or as an id \u2014 or delete it. A directory that ships a manifest and assets and is
|
|
3456
3471
|
reached by nothing reads as an integration to everyone who counts directories, and Midday carried
|
|
3457
|
-
eight of them. Configure \`roots\`, \`registry\` and \`manifests\` \u2014 all three are required
|
|
3458
|
-
|
|
3472
|
+
eight of them. Configure \`roots\`, \`registry\` and \`manifests\` \u2014 all three are required. \`registry\` takes a list when the
|
|
3473
|
+
registry is SPLIT across files (a config that imports a second config): name every half, because the
|
|
3474
|
+
rule reads source and does not follow imports. \`manifests\` has no default on purpose: dielime's plugin directories hold \`service.ts\` and no index, so a defaulted list
|
|
3459
3475
|
would match nothing and report a clean tree it never looked at. A seam whose providers are flat files
|
|
3460
3476
|
rather than directories is out of scope \u2014 nothing under a root matches, so nothing is reported.`,
|
|
3461
3477
|
meta: {
|
|
3462
3478
|
docs: {
|
|
3463
|
-
description: "Every directory under a plugin root must be named by the registry file. Midday ships 35 app-store directories and 8 are reachable from nothing. The registry is read as source, so an id or an import path both count. `roots`, `registry` and `manifests` are all required; `manifests` has no default because a defaulted entry filename matched nothing in a real consumer and reported a clean tree it had never looked at."
|
|
3479
|
+
description: "Every directory under a plugin root must be named by the registry file. Midday ships 35 app-store directories and 8 are reachable from nothing. The registry is read as source, so an id or an import path both count, and `registry` takes a LIST when the registry is split across files \u2014 reading only the named half reported two registered carrier directories as dead weight on a real consumer. `roots`, `registry` and `manifests` are all required; `manifests` has no default because a defaulted entry filename matched nothing in a real consumer and reported a clean tree it had never looked at."
|
|
3464
3480
|
},
|
|
3465
3481
|
messages: {
|
|
3466
3482
|
unregistered: 'The plugin directory "{{directory}}" under `{{root}}` is named nowhere in `{{registry}}`. Nothing reaches it, so it ships as an integration and behaves as dead weight \u2014 register it, or delete it.'
|
|
@@ -3470,7 +3486,12 @@ rather than directories is out of scope \u2014 nothing under a root matches, so
|
|
|
3470
3486
|
additionalProperties: false,
|
|
3471
3487
|
properties: {
|
|
3472
3488
|
manifests: { items: { type: "string" }, type: "array" },
|
|
3473
|
-
registry: {
|
|
3489
|
+
registry: {
|
|
3490
|
+
anyOf: [
|
|
3491
|
+
{ type: "string" },
|
|
3492
|
+
{ items: { type: "string" }, type: "array" }
|
|
3493
|
+
]
|
|
3494
|
+
},
|
|
3474
3495
|
roots: { items: { type: "string" }, type: "array" }
|
|
3475
3496
|
},
|
|
3476
3497
|
type: "object"
|
|
@@ -3480,21 +3501,28 @@ rather than directories is out of scope \u2014 nothing under a root matches, so
|
|
|
3480
3501
|
},
|
|
3481
3502
|
/**
|
|
3482
3503
|
* A directory under a configured root, carrying a manifest, that the registry does not name — and
|
|
3483
|
-
* the
|
|
3504
|
+
* the registry itself, because the rule reads it off disk and returns silently when it is not
|
|
3505
|
+
* there. One file cannot be both the directory under `roots` and the registry at the consumer's
|
|
3506
|
+
* own path, so the probe declares two. It was a single file until 2026-08-30, and every consumer
|
|
3507
|
+
* enforcing this rule over a real registry read as a permanent FAIL for it: the probe fired
|
|
3508
|
+
* nothing, which was the truth about the probe and a lie about the repo.
|
|
3484
3509
|
*
|
|
3485
|
-
* The
|
|
3486
|
-
*
|
|
3487
|
-
*
|
|
3488
|
-
* registry ALSO exists beside it and fires nothing when it does not, which is the truth and is
|
|
3489
|
-
* left as the truth: a probe widened until it fired would be this rule claiming evidence it does
|
|
3490
|
-
* not have, which is precisely the defect the rule is about.
|
|
3510
|
+
* The registry it writes carries no string literal at all, so nothing in it can name the
|
|
3511
|
+
* directory beside it — the probe must be a directory the registry does NOT reach, or the rule is
|
|
3512
|
+
* right to stay quiet.
|
|
3491
3513
|
*/
|
|
3492
3514
|
probe(options) {
|
|
3493
|
-
const { manifests = [], roots = [] } = bagOf(options);
|
|
3515
|
+
const { manifests = [], registry = "", roots = [] } = bagOf(options);
|
|
3494
3516
|
const root = trimSlashes(roots[0] ?? "");
|
|
3517
|
+
const half = typeof registry === "string" ? registry : registry[0] ?? "";
|
|
3495
3518
|
return {
|
|
3496
|
-
|
|
3497
|
-
|
|
3519
|
+
files: [
|
|
3520
|
+
{
|
|
3521
|
+
path: `${root}/geonosis-probe/${manifests[0] ?? ""}`,
|
|
3522
|
+
source: "export const geonosisProbe = { id: 'geonosis-probe' }\n"
|
|
3523
|
+
},
|
|
3524
|
+
{ path: trimSlashes(half), source: "export const registry = { providers: [] }\n" }
|
|
3525
|
+
]
|
|
3498
3526
|
};
|
|
3499
3527
|
}
|
|
3500
3528
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geonosis/oxlint-plugin-biological-architecture",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
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": [
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"oxlint": ">=1.77"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@geonosis/lint-parity": "1.
|
|
46
|
+
"@geonosis/lint-parity": "1.3.0"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
49
|
"node": ">=22"
|