@goodbones/oxlint 0.1.0-beta.7 → 0.1.0-beta.8
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/build/dts/campaigns-rule.d.ts +4 -0
- package/build/dts/campaigns-rule.d.ts.map +1 -0
- package/build/dts/config-loader.d.ts.map +1 -1
- package/build/dts/plugin.d.ts +1 -0
- package/build/dts/plugin.d.ts.map +1 -1
- package/build/esm/campaigns-rule.js +70 -0
- package/build/esm/campaigns-rule.js.map +1 -0
- package/build/esm/config-loader.js +26 -3
- package/build/esm/config-loader.js.map +1 -1
- package/build/esm/plugin.js +2 -0
- package/build/esm/plugin.js.map +1 -1
- package/package.json +4 -3
- package/src/campaigns-rule.ts +98 -0
- package/src/config-loader.ts +27 -2
- package/src/plugin.ts +3 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"campaigns-rule.d.ts","sourceRoot":"","sources":["../../src/campaigns-rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,YAAY,EAElB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAEL,KAAK,UAAU,EAIhB,MAAM,iBAAiB,CAAC;AA2BzB,eAAO,MAAM,iBAAiB,WAAY,YAAY,KAAG,UAoDvD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/config-loader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/config-loader.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,YAAY,EAKlB,MAAM,iBAAiB,CAAC;AAIzB,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAmBpD,eAAO,MAAM,kBAAkB,aACnB,MAAM,mBACC,MAAM,KACtB,OAAO,CAAC,YAAY,CA4BtB,CAAC"}
|
package/build/dts/plugin.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"
|
|
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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { campaignsSelecting, evaluateCampaigns, formatMessage, leafTermsOf, reconcile, } from "@goodbones/core";
|
|
2
|
+
import { sourceFactsOf } from "@goodbones/typescript";
|
|
3
|
+
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) => {
|
|
14
|
+
const carried = new Set();
|
|
15
|
+
for (const rule of selected) {
|
|
16
|
+
const ledger = policy.ledgers.get(rule.id);
|
|
17
|
+
if (ledger === undefined)
|
|
18
|
+
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);
|
|
22
|
+
}
|
|
23
|
+
return hits.filter((hit) => !carried.has(hit.violation));
|
|
24
|
+
};
|
|
25
|
+
export const makeCampaignsRule = (policy) => ({
|
|
26
|
+
meta: {
|
|
27
|
+
type: "problem",
|
|
28
|
+
docs: {
|
|
29
|
+
description: "the campaigns: every place a pattern the repository is migrating away from still occurs, that its ledger does not carry",
|
|
30
|
+
},
|
|
31
|
+
schema: [],
|
|
32
|
+
},
|
|
33
|
+
createOnce(context) {
|
|
34
|
+
let file = "";
|
|
35
|
+
let selected = [];
|
|
36
|
+
return {
|
|
37
|
+
before() {
|
|
38
|
+
file = toRepoRelative(policy.repoRoot, context.filename);
|
|
39
|
+
if (file.startsWith(".."))
|
|
40
|
+
return false;
|
|
41
|
+
selected = campaignsSelecting(policy.campaignRules, file);
|
|
42
|
+
return selected.length > 0;
|
|
43
|
+
},
|
|
44
|
+
Program(node) {
|
|
45
|
+
const text = context.sourceCode.text;
|
|
46
|
+
const needsSyntax = selected.some((rule) => leafTermsOf(rule.detect).some((leaf) => leaf === "syntax" || leaf === "report" || leaf === "fn"));
|
|
47
|
+
const hits = evaluateCampaigns(selected, {
|
|
48
|
+
file,
|
|
49
|
+
text,
|
|
50
|
+
facts: sourceFactsOf(factsOfProgram(file, node)),
|
|
51
|
+
resolver: policy.resolver,
|
|
52
|
+
fileSystem: policy.fileSystem,
|
|
53
|
+
syntax: needsSyntax ? policy.syntax.parse(file, text) : null,
|
|
54
|
+
functions: policy.functions,
|
|
55
|
+
reports: policy.reports,
|
|
56
|
+
});
|
|
57
|
+
for (const hit of unledgered(policy, selected, hits)) {
|
|
58
|
+
// A match lands where it was found; a file-unit hit, on line 1, as
|
|
59
|
+
// the structure rule places a finding about the file itself.
|
|
60
|
+
const at = hit.range ?? { start: { line: 0, column: 0 } };
|
|
61
|
+
context.report({
|
|
62
|
+
message: formatMessage(hit.violation),
|
|
63
|
+
loc: { line: at.start.line + 1, column: at.start.column },
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
//# sourceMappingURL=campaigns-rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"campaigns-rule.js","sourceRoot":"","sources":["../../src/campaigns-rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,kBAAkB,EAElB,iBAAiB,EACjB,aAAa,EACb,WAAW,EAEX,SAAS,GACV,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,OAAO,EACL,cAAc,EAId,cAAc,GACf,MAAM,iBAAiB,CAAC;AAEzB,8EAA8E;AAC9E,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,yEAAyE;AACzE,uEAAuE;AAEvE,yEAAyE;AACzE,uEAAuE;AACvE,+BAA+B;AAC/B,MAAM,UAAU,GAAG,CACjB,MAAoB,EACpB,QAAyC,EACzC,IAAgC,EACJ,EAAE;IAC9B,MAAM,OAAO,GAAG,IAAI,GAAG,EAA4B,CAAC;IACpD,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3C,IAAI,MAAM,KAAK,SAAS;YAAE,SAAS;QACnC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvF,KAAK,MAAM,GAAG,IAAI,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ;YAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;AAC3D,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,MAAoB,EAAc,EAAE,CAAC,CAAC;IACtE,IAAI,EAAE;QACJ,IAAI,EAAE,SAAkB;QACxB,IAAI,EAAE;YACJ,WAAW,EACT,yHAAyH;SAC5H;QACD,MAAM,EAAE,EAAE;KACX;IAED,UAAU,CAAC,OAAoB;QAC7B,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,IAAI,QAAQ,GAAoC,EAAE,CAAC;QAEnD,OAAO;YACL,MAAM;gBACJ,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;gBACzD,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;oBAAE,OAAO,KAAK,CAAC;gBACxC,QAAQ,GAAG,kBAAkB,CAAC,MAAM,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;gBAC1D,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;YAC7B,CAAC;YAED,OAAO,CAAC,IAAa;gBACnB,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC;gBACrC,MAAM,WAAW,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CACzC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAC3B,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,CAClE,CACF,CAAC;gBACF,MAAM,IAAI,GAAG,iBAAiB,CAAC,QAAQ,EAAE;oBACvC,IAAI;oBACJ,IAAI;oBACJ,KAAK,EAAE,aAAa,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;oBAChD,QAAQ,EAAE,MAAM,CAAC,QAAQ;oBACzB,UAAU,EAAE,MAAM,CAAC,UAAU;oBAC7B,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI;oBAC5D,SAAS,EAAE,MAAM,CAAC,SAAS;oBAC3B,OAAO,EAAE,MAAM,CAAC,OAAO;iBACxB,CAAC,CAAC;gBAEH,KAAK,MAAM,GAAG,IAAI,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC;oBACrD,mEAAmE;oBACnE,6DAA6D;oBAC7D,MAAM,EAAE,GAAG,GAAG,CAAC,KAAK,IAAI,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC1D,OAAO,CAAC,MAAM,CAAC;wBACb,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC;wBACrC,GAAG,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE;qBAC1D,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { astGrepMatcher } from "@goodbones/ast-grep";
|
|
3
|
+
import { findManifestFile, loadCampaignFunctions, loadPolicy, makeFileSystemLive, makeReportSourceLive, readManifestFile, } from "@goodbones/core";
|
|
3
4
|
import { typescriptLanguage } from "@goodbones/typescript";
|
|
4
5
|
import * as Result from "effect/Result";
|
|
6
|
+
// The clock the campaigns are judged by; `ARCHITECTURE_NOW` pins it, as it
|
|
7
|
+
// does for the CLI.
|
|
8
|
+
const hostNow = () => {
|
|
9
|
+
const pinned = process.env.ARCHITECTURE_NOW;
|
|
10
|
+
if (pinned === undefined || pinned === "")
|
|
11
|
+
return Date.now();
|
|
12
|
+
const parsed = /^\d+$/.test(pinned) ? Number(pinned) : Date.parse(pinned);
|
|
13
|
+
if (Number.isNaN(parsed)) {
|
|
14
|
+
throw new Error(`ARCHITECTURE_NOW is ${JSON.stringify(pinned)}, which is not a date.`);
|
|
15
|
+
}
|
|
16
|
+
return parsed;
|
|
17
|
+
};
|
|
5
18
|
// The plugin's composition root: read the manifest file, construct the
|
|
6
19
|
// language packs and the live file system, and hand them to the loader. A load
|
|
7
20
|
// failure throws out of here and out of oxlint's import of the plugin — a
|
|
@@ -14,13 +27,23 @@ export const loadPolicyFromFile = async (repoRoot, configFilename) => {
|
|
|
14
27
|
? findManifestFile(repoRoot)
|
|
15
28
|
: path.resolve(repoRoot, configFilename);
|
|
16
29
|
const read = await readManifestFile(configPath);
|
|
30
|
+
// The `fn` terms are imported here, before the policy loads, as the CLI
|
|
31
|
+
// does; the pack is composed with ast-grep for the campaigns family's
|
|
32
|
+
// `syntax` term, and the plugin parses with it for that family only.
|
|
33
|
+
const { functions, manifest } = await loadCampaignFunctions(configPath, read.manifest);
|
|
17
34
|
const loaded = loadPolicy({
|
|
18
35
|
repoRoot,
|
|
19
36
|
configPath,
|
|
20
|
-
manifest
|
|
37
|
+
manifest,
|
|
21
38
|
locate: read.locate,
|
|
22
|
-
languages: [typescriptLanguage()],
|
|
39
|
+
languages: [typescriptLanguage({ syntax: astGrepMatcher() })],
|
|
23
40
|
fileSystem: makeFileSystemLive(repoRoot),
|
|
41
|
+
functions,
|
|
42
|
+
// A `report` command runs once per process — once per editor session
|
|
43
|
+
// for oxlint's language server, which then sees that report until it
|
|
44
|
+
// restarts. A report the build writes to a file is the predictable form.
|
|
45
|
+
reports: makeReportSourceLive(repoRoot),
|
|
46
|
+
now: hostNow(),
|
|
24
47
|
});
|
|
25
48
|
if (Result.isFailure(loaded))
|
|
26
49
|
throw loaded.failure;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../../src/config-loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EACL,gBAAgB,
|
|
1
|
+
{"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../../src/config-loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EAErB,UAAU,EACV,kBAAkB,EAClB,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAIxC,2EAA2E;AAC3E,oBAAoB;AACpB,MAAM,OAAO,GAAG,GAAW,EAAE;IAC3B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAC5C,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7D,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1E,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,wBAAwB,CAAC,CAAC;IACzF,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,uEAAuE;AACvE,+EAA+E;AAC/E,0EAA0E;AAC1E,iEAAiE;AACjE,2CAA2C;AAC3C,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EACrC,QAAgB,EAChB,cAAuB,EACA,EAAE;IACzB,wEAAwE;IACxE,uCAAuC;IACvC,MAAM,UAAU,GACd,cAAc,KAAK,SAAS;QAC1B,CAAC,CAAC,gBAAgB,CAAC,QAAQ,CAAC;QAC5B,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAChD,wEAAwE;IACxE,sEAAsE;IACtE,qEAAqE;IACrE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,MAAM,qBAAqB,CAAC,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvF,MAAM,MAAM,GAAG,UAAU,CAAC;QACxB,QAAQ;QACR,UAAU;QACV,QAAQ;QACR,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,SAAS,EAAE,CAAC,kBAAkB,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,EAAE,CAAC,CAAC;QAC7D,UAAU,EAAE,kBAAkB,CAAC,QAAQ,CAAC;QACxC,SAAS;QACT,qEAAqE;QACrE,qEAAqE;QACrE,yEAAyE;QACzE,OAAO,EAAE,oBAAoB,CAAC,QAAQ,CAAC;QACvC,GAAG,EAAE,OAAO,EAAE;KACf,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;QAAE,MAAM,MAAM,CAAC,OAAO,CAAC;IACnD,OAAO,MAAM,CAAC,OAAO,CAAC;AACxB,CAAC,CAAC"}
|
package/build/esm/plugin.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { makeCampaignsRule } from "./campaigns-rule.js";
|
|
1
2
|
import { loadPolicyFromFile } from "./config-loader.js";
|
|
2
3
|
import { makeExportsRule } from "./exports-rule.js";
|
|
3
4
|
import { makeImportsRule } from "./imports-rule.js";
|
|
@@ -21,6 +22,7 @@ export const rules = {
|
|
|
21
22
|
members: makeMembersRule(policy),
|
|
22
23
|
structure: makeStructureRule(policy),
|
|
23
24
|
surface: makeSurfaceRule(policy),
|
|
25
|
+
campaigns: makeCampaignsRule(policy),
|
|
24
26
|
};
|
|
25
27
|
const plugin = {
|
|
26
28
|
meta: { name: "architecture" },
|
package/build/esm/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"AAAA,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,
|
|
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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodbones/oxlint",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.8",
|
|
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,8 +44,9 @@
|
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"effect": "4.0.0-beta.94",
|
|
47
|
-
"@goodbones/
|
|
48
|
-
"@goodbones/typescript": "0.1.0-beta.
|
|
47
|
+
"@goodbones/ast-grep": "0.1.0",
|
|
48
|
+
"@goodbones/typescript": "0.1.0-beta.8",
|
|
49
|
+
"@goodbones/core": "0.1.0-beta.8"
|
|
49
50
|
},
|
|
50
51
|
"peerDependencies": {
|
|
51
52
|
"oxlint": ">=1.77.0"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type CampaignHit,
|
|
3
|
+
campaignsSelecting,
|
|
4
|
+
type CompiledCampaign,
|
|
5
|
+
evaluateCampaigns,
|
|
6
|
+
formatMessage,
|
|
7
|
+
leafTermsOf,
|
|
8
|
+
type LoadedPolicy,
|
|
9
|
+
reconcile,
|
|
10
|
+
} from "@goodbones/core";
|
|
11
|
+
import { sourceFactsOf } from "@goodbones/typescript";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
factsOfProgram,
|
|
15
|
+
type OxlintRule,
|
|
16
|
+
type Program,
|
|
17
|
+
type RuleContext,
|
|
18
|
+
toRepoRelative,
|
|
19
|
+
} from "./oxlint-api.js";
|
|
20
|
+
|
|
21
|
+
// The campaigns family, one file at a time: every hit of a campaign selecting
|
|
22
|
+
// the file that its ledger does not carry, reported at the match's position
|
|
23
|
+
// with the campaign's `how` as the message. The facts come from oxlint's tree
|
|
24
|
+
// through the pack's reader, as the other rules read them; the `syntax` term
|
|
25
|
+
// is answered by the same matcher the CLI uses, over `sourceCode.text` —
|
|
26
|
+
// "one engine" is this family's parity contract, rather than a corpus.
|
|
27
|
+
|
|
28
|
+
// Whether a hit is carried by the ledger, judged against the file's hits
|
|
29
|
+
// alone: entries are per file, so a file's reconciliation is the whole
|
|
30
|
+
// campaign's restricted to it.
|
|
31
|
+
const unledgered = (
|
|
32
|
+
policy: LoadedPolicy,
|
|
33
|
+
selected: ReadonlyArray<CompiledCampaign>,
|
|
34
|
+
hits: ReadonlyArray<CampaignHit>,
|
|
35
|
+
): ReadonlyArray<CampaignHit> => {
|
|
36
|
+
const carried = new Set<CampaignHit["violation"]>();
|
|
37
|
+
for (const rule of selected) {
|
|
38
|
+
const ledger = policy.ledgers.get(rule.id);
|
|
39
|
+
if (ledger === undefined) continue;
|
|
40
|
+
const own = hits.filter((hit) => hit.campaign === rule.id).map((hit) => hit.violation);
|
|
41
|
+
for (const one of reconcile(ledger, own, rule.unit).ledgered) carried.add(one);
|
|
42
|
+
}
|
|
43
|
+
return hits.filter((hit) => !carried.has(hit.violation));
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const makeCampaignsRule = (policy: LoadedPolicy): OxlintRule => ({
|
|
47
|
+
meta: {
|
|
48
|
+
type: "problem" as const,
|
|
49
|
+
docs: {
|
|
50
|
+
description:
|
|
51
|
+
"the campaigns: every place a pattern the repository is migrating away from still occurs, that its ledger does not carry",
|
|
52
|
+
},
|
|
53
|
+
schema: [],
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
createOnce(context: RuleContext) {
|
|
57
|
+
let file = "";
|
|
58
|
+
let selected: ReadonlyArray<CompiledCampaign> = [];
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
before() {
|
|
62
|
+
file = toRepoRelative(policy.repoRoot, context.filename);
|
|
63
|
+
if (file.startsWith("..")) return false;
|
|
64
|
+
selected = campaignsSelecting(policy.campaignRules, file);
|
|
65
|
+
return selected.length > 0;
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
Program(node: Program) {
|
|
69
|
+
const text = context.sourceCode.text;
|
|
70
|
+
const needsSyntax = selected.some((rule) =>
|
|
71
|
+
leafTermsOf(rule.detect).some(
|
|
72
|
+
(leaf) => leaf === "syntax" || leaf === "report" || leaf === "fn",
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
const hits = evaluateCampaigns(selected, {
|
|
76
|
+
file,
|
|
77
|
+
text,
|
|
78
|
+
facts: sourceFactsOf(factsOfProgram(file, node)),
|
|
79
|
+
resolver: policy.resolver,
|
|
80
|
+
fileSystem: policy.fileSystem,
|
|
81
|
+
syntax: needsSyntax ? policy.syntax.parse(file, text) : null,
|
|
82
|
+
functions: policy.functions,
|
|
83
|
+
reports: policy.reports,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
for (const hit of unledgered(policy, selected, hits)) {
|
|
87
|
+
// A match lands where it was found; a file-unit hit, on line 1, as
|
|
88
|
+
// the structure rule places a finding about the file itself.
|
|
89
|
+
const at = hit.range ?? { start: { line: 0, column: 0 } };
|
|
90
|
+
context.report({
|
|
91
|
+
message: formatMessage(hit.violation),
|
|
92
|
+
loc: { line: at.start.line + 1, column: at.start.column },
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
});
|
package/src/config-loader.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
|
|
3
|
+
import { astGrepMatcher } from "@goodbones/ast-grep";
|
|
3
4
|
import {
|
|
4
5
|
findManifestFile,
|
|
6
|
+
loadCampaignFunctions,
|
|
5
7
|
type LoadedPolicy,
|
|
6
8
|
loadPolicy,
|
|
7
9
|
makeFileSystemLive,
|
|
10
|
+
makeReportSourceLive,
|
|
8
11
|
readManifestFile,
|
|
9
12
|
} from "@goodbones/core";
|
|
10
13
|
import { typescriptLanguage } from "@goodbones/typescript";
|
|
@@ -12,6 +15,18 @@ import * as Result from "effect/Result";
|
|
|
12
15
|
|
|
13
16
|
export type { LoadedPolicy } from "@goodbones/core";
|
|
14
17
|
|
|
18
|
+
// The clock the campaigns are judged by; `ARCHITECTURE_NOW` pins it, as it
|
|
19
|
+
// does for the CLI.
|
|
20
|
+
const hostNow = (): number => {
|
|
21
|
+
const pinned = process.env.ARCHITECTURE_NOW;
|
|
22
|
+
if (pinned === undefined || pinned === "") return Date.now();
|
|
23
|
+
const parsed = /^\d+$/.test(pinned) ? Number(pinned) : Date.parse(pinned);
|
|
24
|
+
if (Number.isNaN(parsed)) {
|
|
25
|
+
throw new Error(`ARCHITECTURE_NOW is ${JSON.stringify(pinned)}, which is not a date.`);
|
|
26
|
+
}
|
|
27
|
+
return parsed;
|
|
28
|
+
};
|
|
29
|
+
|
|
15
30
|
// The plugin's composition root: read the manifest file, construct the
|
|
16
31
|
// language packs and the live file system, and hand them to the loader. A load
|
|
17
32
|
// failure throws out of here and out of oxlint's import of the plugin — a
|
|
@@ -28,13 +43,23 @@ export const loadPolicyFromFile = async (
|
|
|
28
43
|
? findManifestFile(repoRoot)
|
|
29
44
|
: path.resolve(repoRoot, configFilename);
|
|
30
45
|
const read = await readManifestFile(configPath);
|
|
46
|
+
// The `fn` terms are imported here, before the policy loads, as the CLI
|
|
47
|
+
// does; the pack is composed with ast-grep for the campaigns family's
|
|
48
|
+
// `syntax` term, and the plugin parses with it for that family only.
|
|
49
|
+
const { functions, manifest } = await loadCampaignFunctions(configPath, read.manifest);
|
|
31
50
|
const loaded = loadPolicy({
|
|
32
51
|
repoRoot,
|
|
33
52
|
configPath,
|
|
34
|
-
manifest
|
|
53
|
+
manifest,
|
|
35
54
|
locate: read.locate,
|
|
36
|
-
languages: [typescriptLanguage()],
|
|
55
|
+
languages: [typescriptLanguage({ syntax: astGrepMatcher() })],
|
|
37
56
|
fileSystem: makeFileSystemLive(repoRoot),
|
|
57
|
+
functions,
|
|
58
|
+
// A `report` command runs once per process — once per editor session
|
|
59
|
+
// for oxlint's language server, which then sees that report until it
|
|
60
|
+
// restarts. A report the build writes to a file is the predictable form.
|
|
61
|
+
reports: makeReportSourceLive(repoRoot),
|
|
62
|
+
now: hostNow(),
|
|
38
63
|
});
|
|
39
64
|
if (Result.isFailure(loaded)) throw loaded.failure;
|
|
40
65
|
return loaded.success;
|
package/src/plugin.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { makeCampaignsRule } from "./campaigns-rule.js";
|
|
1
2
|
import { loadPolicyFromFile } from "./config-loader.js";
|
|
2
3
|
import { makeExportsRule } from "./exports-rule.js";
|
|
3
4
|
import { makeImportsRule } from "./imports-rule.js";
|
|
@@ -28,12 +29,14 @@ export const rules: {
|
|
|
28
29
|
readonly members: OxlintRule;
|
|
29
30
|
readonly structure: OxlintRule;
|
|
30
31
|
readonly surface: OxlintRule;
|
|
32
|
+
readonly campaigns: OxlintRule;
|
|
31
33
|
} = {
|
|
32
34
|
imports: makeImportsRule(policy),
|
|
33
35
|
exports: makeExportsRule(policy),
|
|
34
36
|
members: makeMembersRule(policy),
|
|
35
37
|
structure: makeStructureRule(policy),
|
|
36
38
|
surface: makeSurfaceRule(policy),
|
|
39
|
+
campaigns: makeCampaignsRule(policy),
|
|
37
40
|
};
|
|
38
41
|
|
|
39
42
|
const plugin: { readonly meta: { readonly name: string }; readonly rules: typeof rules } = {
|