@goodbones/oxlint 0.1.0-beta.10 → 0.1.0-beta.11

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.
@@ -1,4 +1,6 @@
1
+ import { type SectorIndex } from "@goodbones/campaigns";
1
2
  import { type LoadedPolicy } from "@goodbones/core";
2
3
  import { type OxlintRule } from "./oxlint-api.js";
3
- export declare const makeCampaignsRule: (policy: LoadedPolicy) => OxlintRule;
4
+ export type SectorIndexes = ReadonlyMap<string, SectorIndex>;
5
+ export declare const makeCampaignsRule: (policy: LoadedPolicy, indexes?: SectorIndexes) => OxlintRule;
4
6
  //# sourceMappingURL=campaigns-rule.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"campaigns-rule.d.ts","sourceRoot":"","sources":["../../src/campaigns-rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,YAAY,EAGlB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAEL,KAAK,UAAU,EAIhB,MAAM,iBAAiB,CAAC;AAsCzB,eAAO,MAAM,iBAAiB,WAAY,YAAY,KAAG,UAuExD,CAAC"}
1
+ {"version":3,"file":"campaigns-rule.d.ts","sourceRoot":"","sources":["../../src/campaigns-rule.ts"],"names":[],"mappings":"AAEA,OAAO,EAoBL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAiB,KAAK,YAAY,EAAqB,MAAM,iBAAiB,CAAC;AAGtF,OAAO,EAEL,KAAK,UAAU,EAIhB,MAAM,iBAAiB,CAAC;AAsBzB,MAAM,MAAM,aAAa,GAAG,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AA0D7D,eAAO,MAAM,iBAAiB,WACpB,YAAY,YACX,aAAa,KACrB,UA6HF,CAAC"}
@@ -1,4 +1,6 @@
1
+ import { type SectorIndex } from "@goodbones/campaigns";
1
2
  import { type LoadedPolicy } from "@goodbones/core";
2
3
  export type { LoadedPolicy } from "@goodbones/core";
4
+ export declare const discoverSectorIndexes: (policy: LoadedPolicy) => ReadonlyMap<string, SectorIndex>;
3
5
  export declare const loadPolicyFromFile: (repoRoot: string, configFilename?: string) => Promise<LoadedPolicy>;
4
6
  //# sourceMappingURL=config-loader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/config-loader.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,YAAY,EAOlB,MAAM,iBAAiB,CAAC;AAIzB,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAmBpD,eAAO,MAAM,kBAAkB,aACnB,MAAM,mBACC,MAAM,KACtB,OAAO,CAAC,YAAY,CAgDtB,CAAC"}
1
+ {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/config-loader.ts"],"names":[],"mappings":"AAGA,OAAO,EAOL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAKL,KAAK,YAAY,EAKlB,MAAM,iBAAiB,CAAC;AAIzB,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAOpD,eAAO,MAAM,qBAAqB,WAAY,YAAY,KAAG,WAAW,CAAC,MAAM,EAAE,WAAW,CA6B3F,CAAC;AAmBF,eAAO,MAAM,kBAAkB,aACnB,MAAM,mBACC,MAAM,KACtB,OAAO,CAAC,YAAY,CAgDtB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAoBlD,eAAO,MAAM,KAAK,EAAE;IAClB,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;CAQhC,CAAC;AAEF,QAAA,MAAM,MAAM,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,CAAA;CAGrF,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAoBlD,eAAO,MAAM,KAAK,EAAE;IAClB,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;CAUhC,CAAC;AAEF,QAAA,MAAM,MAAM,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,CAAA;CAGrF,CAAC;eAEa,MAAM"}
@@ -1,26 +1,40 @@
1
- import { campaignsSelecting, evaluateCampaigns, formatMessage, leafTermsOf, reconcile, ReportUnavailable, } from "@goodbones/core";
1
+ import * as path from "node:path";
2
+ import { campaignsOf, campaignsSelecting, derivePhase, detectorCandidatesOf, detectorOf, evaluateObjectives, ledgerKeyOf, LEGACY_PHASE, LEGACY_SECTOR, membershipOf, needsSyntax, objectivesInWindow, positionOf, reconcileSector, sectorEntryOf, } from "@goodbones/campaigns";
3
+ import { formatMessage, ReportUnavailable } from "@goodbones/core";
2
4
  import { sourceFactsOf } from "@goodbones/typescript";
3
5
  import { factsOfProgram, toRepoRelative, } from "./oxlint-api.js";
4
- // The campaigns family, one file at a time: every hit of a campaign selecting
5
- // the file that its ledger does not carry, reported at the match's position
6
- // with the campaign's `how` as the message. The facts come from oxlint's tree
7
- // through the pack's reader, as the other rules read them; the `syntax` term
8
- // is answered by the same matcher the CLI uses, over `sourceCode.text` —
9
- // "one engine" is this family's parity contract, rather than a corpus.
10
- // Whether a hit is carried by the ledger, judged against the file's hits
11
- // alone: entries are per file, so a file's reconciliation is the whole
12
- // campaign's restricted to it.
13
- const unledgered = (policy, selected, hits) => {
6
+ const unledgered = (policy, rule, sector, root, hits) => {
14
7
  const carried = new Set();
15
- for (const rule of selected) {
16
- const ledger = policy.ledgers.get(rule.id);
17
- if (ledger === undefined)
8
+ const byObjective = new Map();
9
+ for (const hit of hits) {
10
+ byObjective.set(hit.objective, [...(byObjective.get(hit.objective) ?? []), hit]);
11
+ }
12
+ for (const [objectiveId, own] of byObjective) {
13
+ const objective = rule.objectives.find((one) => one.id === objectiveId);
14
+ const ledger = campaignsOf(policy).ledgers.get(ledgerKeyOf(rule.id, objectiveId));
15
+ if (objective === undefined || ledger === undefined)
18
16
  continue;
19
- const own = hits.filter((hit) => hit.campaign === rule.id).map((hit) => hit.violation);
20
- for (const one of reconcile(ledger, own, rule.unit).ledgered)
21
- carried.add(one);
17
+ const entries = own.map((hit) => sectorEntryOf(hit.violation, root));
18
+ const known = new Set(reconcileSector(ledger, sector, entries, objective.unit).ledgered);
19
+ own.forEach((hit, i) => {
20
+ if (known.has(entries[i] ?? ""))
21
+ carried.add(hit);
22
+ });
22
23
  }
23
- return hits.filter((hit) => !carried.has(hit.violation));
24
+ return hits.filter((hit) => !carried.has(hit));
25
+ };
26
+ // The objectives in window for the sector, read off the ledgers. A sector
27
+ // no `clear` has placed — no record of it — stands at the first phase, as
28
+ // the legacy does, rather than deriving to the end from ledgers that
29
+ // carry nothing for it.
30
+ const inWindowFor = (policy, rule, sector) => {
31
+ const record = campaignsOf(policy).sectorRecords.get(ledgerKeyOf(rule.id, sector));
32
+ const position = positionOf(rule, record);
33
+ const counts = (objectiveId) => campaignsOf(policy).ledgers.get(ledgerKeyOf(rule.id, objectiveId))?.sectors[sector]?.holdouts.length ?? 0;
34
+ const phase = sector === LEGACY_SECTOR || record === undefined
35
+ ? Math.min(LEGACY_PHASE, rule.phases.length)
36
+ : derivePhase(rule, counts, position);
37
+ return objectivesInWindow(rule, phase, position).filter((one) => one.detect !== null);
24
38
  };
25
39
  // A `report` a campaign names that cannot be read — a command the kernel
26
40
  // refused to spawn, a file no step wrote — is one fact about the run, not
@@ -31,15 +45,16 @@ const unledgered = (policy, selected, hits) => {
31
45
  // "error running JS plugin", with the cause dropped by the terser formats.
32
46
  const aside = (cause) => `A report a campaign names could not be read, so no campaign naming it is judged in this ` +
33
47
  `run: ${cause.message}`;
34
- export const makeCampaignsRule = (policy) => {
48
+ export const makeCampaignsRule = (policy, indexes = new Map()) => {
35
49
  // Per rule instance, which is per plugin load — one process — rather than
36
50
  // per `createOnce`, which a host may call more often than that.
37
51
  const said = new Set();
52
+ const known = new Set(policy.languages.flatMap((one) => one.extensions));
38
53
  return {
39
54
  meta: {
40
55
  type: "problem",
41
56
  docs: {
42
- description: "the campaigns: every place a pattern the repository is migrating away from still occurs, that its ledger does not carry",
57
+ description: "the campaigns: every place a pattern the repository is migrating away from still occurs, in a sector whose phase asks for it, that its ledger does not carry",
43
58
  },
44
59
  schema: [],
45
60
  },
@@ -51,29 +66,76 @@ export const makeCampaignsRule = (policy) => {
51
66
  file = toRepoRelative(policy.repoRoot, context.filename);
52
67
  if (file.startsWith(".."))
53
68
  return false;
54
- selected = campaignsSelecting(policy.campaignRules, file);
69
+ // A file the packs would not walk — one oxlint lints anyway — is
70
+ // seen only by a campaign that widened its scope to it, so both
71
+ // hosts answer about the same files.
72
+ const extension = path.extname(file);
73
+ selected = campaignsSelecting(campaignsOf(policy).campaignRules, file).filter((rule) => known.size === 0 || known.has(extension) || rule.extensions.includes(extension));
55
74
  return selected.length > 0;
56
75
  },
57
76
  Program(node) {
58
77
  const text = context.sourceCode.text;
59
- const needsSyntax = selected.some((rule) => leafTermsOf(rule.detect).some((leaf) => leaf === "syntax" || leaf === "report" || leaf === "fn"));
78
+ const detectors = selected.flatMap((rule) => [
79
+ ...rule.objectives.flatMap((one) => {
80
+ const detect = detectorOf(one);
81
+ return detect === null ? [] : [detect];
82
+ }),
83
+ ...(rule.perimeter?.kind === "match" ? [rule.perimeter.detect] : []),
84
+ ]);
60
85
  const input = {
61
86
  file,
62
87
  text,
63
88
  facts: sourceFactsOf(factsOfProgram(file, node)),
64
89
  resolver: policy.resolver,
65
90
  fileSystem: policy.fileSystem,
66
- syntax: needsSyntax ? policy.syntax.parse(file, text) : null,
67
- functions: policy.functions,
68
- reports: policy.reports,
91
+ syntax: needsSyntax(detectors) ? policy.syntax.parse(file, text) : null,
92
+ functions: campaignsOf(policy).functions,
93
+ reports: campaignsOf(policy).reports,
69
94
  };
70
95
  // One campaign at a time, so a report one of them cannot read costs
71
96
  // that campaign's answer for this file and not its neighbours'.
72
- const hits = [];
97
+ const unrecorded = [];
73
98
  for (const rule of selected) {
74
99
  try {
75
- for (const hit of evaluateCampaigns([rule], input))
76
- hits.push(hit);
100
+ const perimeter = rule.perimeter;
101
+ const anchors = perimeter?.kind === "match"
102
+ ? detectorCandidatesOf(perimeter.detect, perimeter.unit, input).map((one) => one.key.split("#")[0] ?? one.key)
103
+ : [];
104
+ const place = membershipOf(rule, file, indexes.get(rule.id) ?? null, anchors);
105
+ // Hits grouped by the sector they fall in, then judged
106
+ // against that sector's window and ledger.
107
+ const bySector = new Map();
108
+ const windows = new Map();
109
+ const placedAt = (subject) => {
110
+ const placement = place(subject);
111
+ if (placement === null)
112
+ return null;
113
+ if (!windows.has(placement.sector)) {
114
+ windows.set(placement.sector, new Set(inWindowFor(policy, rule, placement.sector).map((one) => one.id)));
115
+ }
116
+ return placement;
117
+ };
118
+ const whole = placedAt(null);
119
+ const candidates = whole === null
120
+ ? rule.objectives.filter((one) => one.detect !== null)
121
+ : rule.objectives.filter((one) => one.detect !== null && (windows.get(whole.sector)?.has(one.id) ?? false));
122
+ if (perimeter?.kind !== "match" && whole === null)
123
+ continue;
124
+ for (const hit of evaluateObjectives(candidates, input)) {
125
+ const placement = placedAt(hit.violation.subject);
126
+ if (placement === null)
127
+ continue;
128
+ if (!(windows.get(placement.sector)?.has(hit.objective) ?? false))
129
+ continue;
130
+ const group = bySector.get(placement.sector) ?? { root: placement.root, hits: [] };
131
+ group.hits.push(hit);
132
+ bySector.set(placement.sector, group);
133
+ }
134
+ for (const [sector, group] of bySector) {
135
+ for (const hit of unledgered(policy, rule, sector, group.root, group.hits)) {
136
+ unrecorded.push(hit);
137
+ }
138
+ }
77
139
  }
78
140
  catch (cause) {
79
141
  if (!(cause instanceof ReportUnavailable))
@@ -85,7 +147,7 @@ export const makeCampaignsRule = (policy) => {
85
147
  context.report({ message, loc: { line: 1, column: 0 } });
86
148
  }
87
149
  }
88
- for (const hit of unledgered(policy, selected, hits)) {
150
+ for (const hit of unrecorded) {
89
151
  // A match lands where it was found; a file-unit hit, on line 1, as
90
152
  // the structure rule places a finding about the file itself.
91
153
  const at = hit.range ?? { start: { line: 0, column: 0 } };
@@ -1 +1 @@
1
- {"version":3,"file":"campaigns-rule.js","sourceRoot":"","sources":["../../src/campaigns-rule.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"campaigns-rule.js","sourceRoot":"","sources":["../../src/campaigns-rule.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,37 @@
1
1
  import * as path from "node:path";
2
2
  import { astGrepMatcher } from "@goodbones/ast-grep";
3
- import { findManifestFile, loadCampaignFunctions, loadPolicy, makeFileSystemLive, makeReportSourceLive, readManifestFile, reportSpecsOf, ReportUnavailable, } from "@goodbones/core";
3
+ import { campaignsExtension, campaignsOf, discoverSectors, loadCampaignFunctions, makeReportSourceLive, reportSpecsOf, } from "@goodbones/campaigns";
4
+ import { findManifestFile, globToRegExp, listSourceFiles, listWorkspaceProjects, loadPolicy, makeFileSystemLive, readManifestFile, ReportUnavailable, } from "@goodbones/core";
4
5
  import { typescriptLanguage } from "@goodbones/typescript";
5
6
  import * as Result from "effect/Result";
7
+ // A `marker` or `nx` perimeter needs the other files to say which sector a
8
+ // file is in — the markers, or the workspace's projects — so the plugin
9
+ // reads them once at load, from a walk of the repository that reads no
10
+ // source text but the markers'. The other perimeters answer from the path
11
+ // or the file itself, and need nothing here.
12
+ export const discoverSectorIndexes = (policy) => {
13
+ const indexes = new Map();
14
+ const needing = campaignsOf(policy).campaignRules.filter((rule) => rule.perimeter?.kind === "marker" || rule.perimeter?.kind === "nx");
15
+ if (needing.length === 0)
16
+ return indexes;
17
+ const widened = [...new Set(needing.flatMap((rule) => rule.extensions))];
18
+ const files = listSourceFiles(policy.repoRoot, ["."], policy.languages, widened);
19
+ const known = new Set(policy.languages.flatMap((one) => one.extensions));
20
+ const projects = needing.some((rule) => rule.perimeter?.kind === "nx")
21
+ ? listWorkspaceProjects(policy.repoRoot, ["."])
22
+ : [];
23
+ for (const rule of needing) {
24
+ const scoped = files.filter((file) => rule.scope.some((pattern) => pattern.test(file)) &&
25
+ (known.has(path.extname(file)) || rule.extensions.includes(path.extname(file))));
26
+ indexes.set(rule.id, discoverSectors(rule, {
27
+ files: scoped,
28
+ readText: (file) => policy.fileSystem.readText(file),
29
+ globToRegExp,
30
+ projects,
31
+ }));
32
+ }
33
+ return indexes;
34
+ };
6
35
  // The clock the campaigns are judged by; `ARCHITECTURE_NOW` pins it, as it
7
36
  // does for the CLI.
8
37
  const hostNow = () => {
@@ -38,11 +67,10 @@ export const loadPolicyFromFile = async (repoRoot, configFilename) => {
38
67
  locate: read.locate,
39
68
  languages: [typescriptLanguage({ syntax: astGrepMatcher() })],
40
69
  fileSystem: makeFileSystemLive(repoRoot),
41
- functions,
42
70
  // A `report` command runs once per process — once per editor session
43
71
  // for oxlint's language server, which then sees that report until it
44
72
  // restarts. A report the build writes to a file is the predictable form.
45
- reports: makeReportSourceLive(repoRoot),
73
+ extensions: [campaignsExtension({ functions, reports: makeReportSourceLive(repoRoot) })],
46
74
  now: hostNow(),
47
75
  });
48
76
  if (Result.isFailure(loaded))
@@ -57,11 +85,12 @@ export const loadPolicyFromFile = async (repoRoot, configFilename) => {
57
85
  // commands run at once. A report that cannot be read is not a load
58
86
  // failure — the campaigns rule reports it once, on the first file a
59
87
  // campaign naming it selects; one that does not parse is.
60
- await Promise.all(reportSpecsOf(policy.campaignRules).map(async (spec) => {
61
- if (policy.reports.read !== undefined)
62
- return policy.reports.read(spec);
88
+ const campaigns = campaignsOf(policy);
89
+ await Promise.all(reportSpecsOf(campaigns.campaignRules).map(async (spec) => {
90
+ if (campaigns.reports.read !== undefined)
91
+ return campaigns.reports.read(spec);
63
92
  try {
64
- policy.reports.diagnosticsOf(spec, "");
93
+ campaigns.reports.diagnosticsOf(spec, "");
65
94
  }
66
95
  catch (cause) {
67
96
  if (!(cause instanceof ReportUnavailable))
@@ -1 +1 @@
1
- {"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../../src/config-loader.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../../src/config-loader.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { makeCampaignsRule } from "./campaigns-rule.js";
2
- import { loadPolicyFromFile } from "./config-loader.js";
2
+ import { discoverSectorIndexes, loadPolicyFromFile } from "./config-loader.js";
3
3
  import { makeExportsRule } from "./exports-rule.js";
4
4
  import { makeImportsRule } from "./imports-rule.js";
5
5
  import { makeMembersRule } from "./members-rule.js";
@@ -22,7 +22,9 @@ export const rules = {
22
22
  members: makeMembersRule(policy),
23
23
  structure: makeStructureRule(policy),
24
24
  surface: makeSurfaceRule(policy),
25
- campaigns: makeCampaignsRule(policy),
25
+ // The sectors a `marker` or `nx` perimeter births are read once here,
26
+ // from the markers and the workspace, before any file is linted.
27
+ campaigns: makeCampaignsRule(policy, discoverSectorIndexes(policy)),
26
28
  };
27
29
  const plugin = {
28
30
  meta: { name: "architecture" },
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,gFAAgF;AAChF,gFAAgF;AAChF,+EAA+E;AAC/E,+EAA+E;AAC/E,YAAY;AACZ,MAAM,MAAM,GAAG,MAAM,kBAAkB,CACrC,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,OAAO,CAAC,GAAG,EAAE,EAC9C,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAChC,CAAC;AAEF,6EAA6E;AAC7E,mCAAmC;AACnC,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;IACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8BAA8B,MAAM,IAAI,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAOd;IACF,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC;IAChC,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC;IAChC,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC;IAChC,SAAS,EAAE,iBAAiB,CAAC,MAAM,CAAC;IACpC,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC;IAChC,SAAS,EAAE,iBAAiB,CAAC,MAAM,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,GAA+E;IACzF,IAAI,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE;IAC9B,KAAK;CACN,CAAC;AAEF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,gFAAgF;AAChF,gFAAgF;AAChF,+EAA+E;AAC/E,+EAA+E;AAC/E,YAAY;AACZ,MAAM,MAAM,GAAG,MAAM,kBAAkB,CACrC,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,OAAO,CAAC,GAAG,EAAE,EAC9C,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAChC,CAAC;AAEF,6EAA6E;AAC7E,mCAAmC;AACnC,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;IACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8BAA8B,MAAM,IAAI,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAOd;IACF,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC;IAChC,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC;IAChC,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC;IAChC,SAAS,EAAE,iBAAiB,CAAC,MAAM,CAAC;IACpC,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC;IAChC,sEAAsE;IACtE,iEAAiE;IACjE,SAAS,EAAE,iBAAiB,CAAC,MAAM,EAAE,qBAAqB,CAAC,MAAM,CAAC,CAAC;CACpE,CAAC;AAEF,MAAM,MAAM,GAA+E;IACzF,IAAI,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE;IAC9B,KAAK;CACN,CAAC;AAEF,eAAe,MAAM,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodbones/oxlint",
3
- "version": "0.1.0-beta.10",
3
+ "version": "0.1.0-beta.11",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "The oxlint plugin for @goodbones/core: the imports, exports, members, structure and surface families as five oxlint rules, evaluated in the editor and in CI from the same manifest the CLI reads.",
@@ -44,9 +44,10 @@
44
44
  ],
45
45
  "dependencies": {
46
46
  "effect": "4.0.0-beta.94",
47
- "@goodbones/ast-grep": "0.1.2",
48
- "@goodbones/core": "0.1.0-beta.10",
49
- "@goodbones/typescript": "0.1.0-beta.10"
47
+ "@goodbones/ast-grep": "1.0.0",
48
+ "@goodbones/campaigns": "0.1.0",
49
+ "@goodbones/typescript": "0.1.0-beta.11",
50
+ "@goodbones/core": "0.1.0-beta.11"
50
51
  },
51
52
  "peerDependencies": {
52
53
  "oxlint": ">=1.77.0"
@@ -1,14 +1,28 @@
1
+ import * as path from "node:path";
2
+
1
3
  import {
2
4
  type CampaignHit,
5
+ type CampaignInput,
6
+ campaignsOf,
3
7
  campaignsSelecting,
4
8
  type CompiledCampaign,
5
- evaluateCampaigns,
6
- formatMessage,
7
- leafTermsOf,
8
- type LoadedPolicy,
9
- reconcile,
10
- ReportUnavailable,
11
- } from "@goodbones/core";
9
+ type CompiledObjective,
10
+ derivePhase,
11
+ detectorCandidatesOf,
12
+ detectorOf,
13
+ evaluateObjectives,
14
+ ledgerKeyOf,
15
+ LEGACY_PHASE,
16
+ LEGACY_SECTOR,
17
+ membershipOf,
18
+ needsSyntax,
19
+ objectivesInWindow,
20
+ positionOf,
21
+ reconcileSector,
22
+ sectorEntryOf,
23
+ type SectorIndex,
24
+ } from "@goodbones/campaigns";
25
+ import { formatMessage, type LoadedPolicy, ReportUnavailable } from "@goodbones/core";
12
26
  import { sourceFactsOf } from "@goodbones/typescript";
13
27
 
14
28
  import {
@@ -19,29 +33,71 @@ import {
19
33
  toRepoRelative,
20
34
  } from "./oxlint-api.js";
21
35
 
22
- // The campaigns family, one file at a time: every hit of a campaign selecting
23
- // the file that its ledger does not carry, reported at the match's position
24
- // with the campaign's `how` as the message. The facts come from oxlint's tree
25
- // through the pack's reader, as the other rules read them; the `syntax` term
26
- // is answered by the same matcher the CLI uses, over `sourceCode.text` —
27
- // "one engine" is this family's parity contract, rather than a corpus.
36
+ // The campaigns family, one file at a time: every hit of an objective in
37
+ // window for the file's sector that its ledger does not carry, reported at
38
+ // the match's position with the objective's `how` as the message. The
39
+ // facts come from oxlint's tree through the pack's reader, as the other
40
+ // rules read them; the `syntax` term is answered by the same matcher the
41
+ // CLI uses, over `sourceCode.text` — "one engine" is this family's parity
42
+ // contract, rather than a corpus.
43
+ //
44
+ // The sector comes from the perimeter on this file (a marker or nx index
45
+ // the host built at load, for the two forms that need the other files),
46
+ // and the sector's phase from the ledgers — which objectives carry
47
+ // holdouts for it, and its record's `reached` and attestations — never
48
+ // from a walk. So the plugin's answer is the code as of the last `clear`,
49
+ // and the CLI's is the code now; between a fix and its `clear` they
50
+ // differ, and `check` fails on the stale holdout until they agree. A
51
+ // `has` objective and an end state are never reported here: their holdout
52
+ // is the sector, and they reach this file only as the phase they put it at.
53
+
54
+ // The sectors a host discovered at load, per campaign, for the perimeters
55
+ // that need the other files.
56
+ export type SectorIndexes = ReadonlyMap<string, SectorIndex>;
28
57
 
29
- // Whether a hit is carried by the ledger, judged against the file's hits
30
- // alone: entries are per file, so a file's reconciliation is the whole
31
- // campaign's restricted to it.
32
58
  const unledgered = (
33
59
  policy: LoadedPolicy,
34
- selected: ReadonlyArray<CompiledCampaign>,
60
+ rule: CompiledCampaign,
61
+ sector: string,
62
+ root: string,
35
63
  hits: ReadonlyArray<CampaignHit>,
36
64
  ): ReadonlyArray<CampaignHit> => {
37
- const carried = new Set<CampaignHit["violation"]>();
38
- for (const rule of selected) {
39
- const ledger = policy.ledgers.get(rule.id);
40
- if (ledger === undefined) continue;
41
- const own = hits.filter((hit) => hit.campaign === rule.id).map((hit) => hit.violation);
42
- for (const one of reconcile(ledger, own, rule.unit).ledgered) carried.add(one);
65
+ const carried = new Set<CampaignHit>();
66
+ const byObjective = new Map<string, Array<CampaignHit>>();
67
+ for (const hit of hits) {
68
+ byObjective.set(hit.objective, [...(byObjective.get(hit.objective) ?? []), hit]);
69
+ }
70
+ for (const [objectiveId, own] of byObjective) {
71
+ const objective = rule.objectives.find((one) => one.id === objectiveId);
72
+ const ledger = campaignsOf(policy).ledgers.get(ledgerKeyOf(rule.id, objectiveId));
73
+ if (objective === undefined || ledger === undefined) continue;
74
+ const entries = own.map((hit) => sectorEntryOf(hit.violation, root));
75
+ const known = new Set(reconcileSector(ledger, sector, entries, objective.unit).ledgered);
76
+ own.forEach((hit, i) => {
77
+ if (known.has(entries[i] ?? "")) carried.add(hit);
78
+ });
43
79
  }
44
- return hits.filter((hit) => !carried.has(hit.violation));
80
+ return hits.filter((hit) => !carried.has(hit));
81
+ };
82
+
83
+ // The objectives in window for the sector, read off the ledgers. A sector
84
+ // no `clear` has placed — no record of it — stands at the first phase, as
85
+ // the legacy does, rather than deriving to the end from ledgers that
86
+ // carry nothing for it.
87
+ const inWindowFor = (
88
+ policy: LoadedPolicy,
89
+ rule: CompiledCampaign,
90
+ sector: string,
91
+ ): ReadonlyArray<CompiledObjective> => {
92
+ const record = campaignsOf(policy).sectorRecords.get(ledgerKeyOf(rule.id, sector));
93
+ const position = positionOf(rule, record);
94
+ const counts = (objectiveId: string): number =>
95
+ campaignsOf(policy).ledgers.get(ledgerKeyOf(rule.id, objectiveId))?.sectors[sector]?.holdouts.length ?? 0;
96
+ const phase =
97
+ sector === LEGACY_SECTOR || record === undefined
98
+ ? Math.min(LEGACY_PHASE, rule.phases.length)
99
+ : derivePhase(rule, counts, position);
100
+ return objectivesInWindow(rule, phase, position).filter((one) => one.detect !== null);
45
101
  };
46
102
 
47
103
  // A `report` a campaign names that cannot be read — a command the kernel
@@ -55,16 +111,20 @@ const aside = (cause: ReportUnavailable): string =>
55
111
  `A report a campaign names could not be read, so no campaign naming it is judged in this ` +
56
112
  `run: ${cause.message}`;
57
113
 
58
- export const makeCampaignsRule = (policy: LoadedPolicy): OxlintRule => {
114
+ export const makeCampaignsRule = (
115
+ policy: LoadedPolicy,
116
+ indexes: SectorIndexes = new Map(),
117
+ ): OxlintRule => {
59
118
  // Per rule instance, which is per plugin load — one process — rather than
60
119
  // per `createOnce`, which a host may call more often than that.
61
120
  const said = new Set<string>();
121
+ const known = new Set(policy.languages.flatMap((one) => one.extensions));
62
122
  return {
63
123
  meta: {
64
124
  type: "problem" as const,
65
125
  docs: {
66
126
  description:
67
- "the campaigns: every place a pattern the repository is migrating away from still occurs, that its ledger does not carry",
127
+ "the campaigns: every place a pattern the repository is migrating away from still occurs, in a sector whose phase asks for it, that its ledger does not carry",
68
128
  },
69
129
  schema: [],
70
130
  },
@@ -77,33 +137,86 @@ export const makeCampaignsRule = (policy: LoadedPolicy): OxlintRule => {
77
137
  before() {
78
138
  file = toRepoRelative(policy.repoRoot, context.filename);
79
139
  if (file.startsWith("..")) return false;
80
- selected = campaignsSelecting(policy.campaignRules, file);
140
+ // A file the packs would not walk — one oxlint lints anyway — is
141
+ // seen only by a campaign that widened its scope to it, so both
142
+ // hosts answer about the same files.
143
+ const extension = path.extname(file);
144
+ selected = campaignsSelecting(campaignsOf(policy).campaignRules, file).filter(
145
+ (rule) =>
146
+ known.size === 0 || known.has(extension) || rule.extensions.includes(extension),
147
+ );
81
148
  return selected.length > 0;
82
149
  },
83
150
 
84
151
  Program(node: Program) {
85
152
  const text = context.sourceCode.text;
86
- const needsSyntax = selected.some((rule) =>
87
- leafTermsOf(rule.detect).some(
88
- (leaf) => leaf === "syntax" || leaf === "report" || leaf === "fn",
89
- ),
90
- );
91
- const input = {
153
+ const detectors = selected.flatMap((rule) => [
154
+ ...rule.objectives.flatMap((one) => {
155
+ const detect = detectorOf(one);
156
+ return detect === null ? [] : [detect];
157
+ }),
158
+ ...(rule.perimeter?.kind === "match" ? [rule.perimeter.detect] : []),
159
+ ]);
160
+ const input: CampaignInput = {
92
161
  file,
93
162
  text,
94
163
  facts: sourceFactsOf(factsOfProgram(file, node)),
95
164
  resolver: policy.resolver,
96
165
  fileSystem: policy.fileSystem,
97
- syntax: needsSyntax ? policy.syntax.parse(file, text) : null,
98
- functions: policy.functions,
99
- reports: policy.reports,
166
+ syntax: needsSyntax(detectors) ? policy.syntax.parse(file, text) : null,
167
+ functions: campaignsOf(policy).functions,
168
+ reports: campaignsOf(policy).reports,
100
169
  };
101
170
  // One campaign at a time, so a report one of them cannot read costs
102
171
  // that campaign's answer for this file and not its neighbours'.
103
- const hits: Array<CampaignHit> = [];
172
+ const unrecorded: Array<CampaignHit> = [];
104
173
  for (const rule of selected) {
105
174
  try {
106
- for (const hit of evaluateCampaigns([rule], input)) hits.push(hit);
175
+ const perimeter = rule.perimeter;
176
+ const anchors =
177
+ perimeter?.kind === "match"
178
+ ? detectorCandidatesOf(perimeter.detect, perimeter.unit, input).map(
179
+ (one) => one.key.split("#")[0] ?? one.key,
180
+ )
181
+ : [];
182
+ const place = membershipOf(rule, file, indexes.get(rule.id) ?? null, anchors);
183
+ // Hits grouped by the sector they fall in, then judged
184
+ // against that sector's window and ledger.
185
+ const bySector = new Map<string, { root: string; hits: Array<CampaignHit> }>();
186
+ const windows = new Map<string, ReadonlySet<string>>();
187
+ const placedAt = (subject: string | null) => {
188
+ const placement = place(subject);
189
+ if (placement === null) return null;
190
+ if (!windows.has(placement.sector)) {
191
+ windows.set(
192
+ placement.sector,
193
+ new Set(inWindowFor(policy, rule, placement.sector).map((one) => one.id)),
194
+ );
195
+ }
196
+ return placement;
197
+ };
198
+ const whole = placedAt(null);
199
+ const candidates =
200
+ whole === null
201
+ ? rule.objectives.filter((one) => one.detect !== null)
202
+ : rule.objectives.filter(
203
+ (one) =>
204
+ one.detect !== null && (windows.get(whole.sector)?.has(one.id) ?? false),
205
+ );
206
+ if (perimeter?.kind !== "match" && whole === null) continue;
207
+ for (const hit of evaluateObjectives(candidates, input)) {
208
+ const placement = placedAt(hit.violation.subject);
209
+ if (placement === null) continue;
210
+ if (!(windows.get(placement.sector)?.has(hit.objective) ?? false)) continue;
211
+ const group = bySector.get(placement.sector) ?? { root: placement.root, hits: [] };
212
+ group.hits.push(hit);
213
+ bySector.set(placement.sector, group);
214
+ }
215
+ for (const [sector, group] of bySector) {
216
+ for (const hit of unledgered(policy, rule, sector, group.root, group.hits)) {
217
+ unrecorded.push(hit);
218
+ }
219
+ }
107
220
  } catch (cause) {
108
221
  if (!(cause instanceof ReportUnavailable)) throw cause;
109
222
  const message = aside(cause);
@@ -113,7 +226,7 @@ export const makeCampaignsRule = (policy: LoadedPolicy): OxlintRule => {
113
226
  }
114
227
  }
115
228
 
116
- for (const hit of unledgered(policy, selected, hits)) {
229
+ for (const hit of unrecorded) {
117
230
  // A match lands where it was found; a file-unit hit, on line 1, as
118
231
  // the structure rule places a finding about the file itself.
119
232
  const at = hit.range ?? { start: { line: 0, column: 0 } };
@@ -2,14 +2,23 @@ import * as path from "node:path";
2
2
 
3
3
  import { astGrepMatcher } from "@goodbones/ast-grep";
4
4
  import {
5
- findManifestFile,
5
+ campaignsExtension,
6
+ campaignsOf,
7
+ discoverSectors,
6
8
  loadCampaignFunctions,
9
+ makeReportSourceLive,
10
+ reportSpecsOf,
11
+ type SectorIndex,
12
+ } from "@goodbones/campaigns";
13
+ import {
14
+ findManifestFile,
15
+ globToRegExp,
16
+ listSourceFiles,
17
+ listWorkspaceProjects,
7
18
  type LoadedPolicy,
8
19
  loadPolicy,
9
20
  makeFileSystemLive,
10
- makeReportSourceLive,
11
21
  readManifestFile,
12
- reportSpecsOf,
13
22
  ReportUnavailable,
14
23
  } from "@goodbones/core";
15
24
  import { typescriptLanguage } from "@goodbones/typescript";
@@ -17,6 +26,42 @@ import * as Result from "effect/Result";
17
26
 
18
27
  export type { LoadedPolicy } from "@goodbones/core";
19
28
 
29
+ // A `marker` or `nx` perimeter needs the other files to say which sector a
30
+ // file is in — the markers, or the workspace's projects — so the plugin
31
+ // reads them once at load, from a walk of the repository that reads no
32
+ // source text but the markers'. The other perimeters answer from the path
33
+ // or the file itself, and need nothing here.
34
+ export const discoverSectorIndexes = (policy: LoadedPolicy): ReadonlyMap<string, SectorIndex> => {
35
+ const indexes = new Map<string, SectorIndex>();
36
+ const needing = campaignsOf(policy).campaignRules.filter(
37
+ (rule) => rule.perimeter?.kind === "marker" || rule.perimeter?.kind === "nx",
38
+ );
39
+ if (needing.length === 0) return indexes;
40
+ const widened = [...new Set(needing.flatMap((rule) => rule.extensions))];
41
+ const files = listSourceFiles(policy.repoRoot, ["."], policy.languages, widened);
42
+ const known = new Set(policy.languages.flatMap((one) => one.extensions));
43
+ const projects = needing.some((rule) => rule.perimeter?.kind === "nx")
44
+ ? listWorkspaceProjects(policy.repoRoot, ["."])
45
+ : [];
46
+ for (const rule of needing) {
47
+ const scoped = files.filter(
48
+ (file) =>
49
+ rule.scope.some((pattern) => pattern.test(file)) &&
50
+ (known.has(path.extname(file)) || rule.extensions.includes(path.extname(file))),
51
+ );
52
+ indexes.set(
53
+ rule.id,
54
+ discoverSectors(rule, {
55
+ files: scoped,
56
+ readText: (file) => policy.fileSystem.readText(file),
57
+ globToRegExp,
58
+ projects,
59
+ }),
60
+ );
61
+ }
62
+ return indexes;
63
+ };
64
+
20
65
  // The clock the campaigns are judged by; `ARCHITECTURE_NOW` pins it, as it
21
66
  // does for the CLI.
22
67
  const hostNow = (): number => {
@@ -56,11 +101,10 @@ export const loadPolicyFromFile = async (
56
101
  locate: read.locate,
57
102
  languages: [typescriptLanguage({ syntax: astGrepMatcher() })],
58
103
  fileSystem: makeFileSystemLive(repoRoot),
59
- functions,
60
104
  // A `report` command runs once per process — once per editor session
61
105
  // for oxlint's language server, which then sees that report until it
62
106
  // restarts. A report the build writes to a file is the predictable form.
63
- reports: makeReportSourceLive(repoRoot),
107
+ extensions: [campaignsExtension({ functions, reports: makeReportSourceLive(repoRoot) })],
64
108
  now: hostNow(),
65
109
  });
66
110
  if (Result.isFailure(loaded)) throw loaded.failure;
@@ -74,11 +118,12 @@ export const loadPolicyFromFile = async (
74
118
  // commands run at once. A report that cannot be read is not a load
75
119
  // failure — the campaigns rule reports it once, on the first file a
76
120
  // campaign naming it selects; one that does not parse is.
121
+ const campaigns = campaignsOf(policy);
77
122
  await Promise.all(
78
- reportSpecsOf(policy.campaignRules).map(async (spec) => {
79
- if (policy.reports.read !== undefined) return policy.reports.read(spec);
123
+ reportSpecsOf(campaigns.campaignRules).map(async (spec) => {
124
+ if (campaigns.reports.read !== undefined) return campaigns.reports.read(spec);
80
125
  try {
81
- policy.reports.diagnosticsOf(spec, "");
126
+ campaigns.reports.diagnosticsOf(spec, "");
82
127
  } catch (cause) {
83
128
  if (!(cause instanceof ReportUnavailable)) throw cause;
84
129
  }
package/src/plugin.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { makeCampaignsRule } from "./campaigns-rule.js";
2
- import { loadPolicyFromFile } from "./config-loader.js";
2
+ import { discoverSectorIndexes, loadPolicyFromFile } from "./config-loader.js";
3
3
  import { makeExportsRule } from "./exports-rule.js";
4
4
  import { makeImportsRule } from "./imports-rule.js";
5
5
  import { makeMembersRule } from "./members-rule.js";
@@ -36,7 +36,9 @@ export const rules: {
36
36
  members: makeMembersRule(policy),
37
37
  structure: makeStructureRule(policy),
38
38
  surface: makeSurfaceRule(policy),
39
- campaigns: makeCampaignsRule(policy),
39
+ // The sectors a `marker` or `nx` perimeter births are read once here,
40
+ // from the markers and the workspace, before any file is linted.
41
+ campaigns: makeCampaignsRule(policy, discoverSectorIndexes(policy)),
40
42
  };
41
43
 
42
44
  const plugin: { readonly meta: { readonly name: string }; readonly rules: typeof rules } = {