@shivaedev/quality 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +69 -2
- package/dist/rules/suppressions/biome/configs.d.ts +21 -10
- package/dist/rules/suppressions/biome/configs.d.ts.map +1 -1
- package/dist/rules/suppressions/biome/configs.js +57 -21
- package/dist/rules/suppressions/biome/configs.js.map +1 -1
- package/dist/rules/suppressions/biome/declarations.d.ts +18 -0
- package/dist/rules/suppressions/biome/declarations.d.ts.map +1 -0
- package/dist/rules/suppressions/biome/declarations.js +20 -0
- package/dist/rules/suppressions/biome/declarations.js.map +1 -0
- package/dist/rules/suppressions/biome/resolve.d.ts +3 -0
- package/dist/rules/suppressions/biome/resolve.d.ts.map +1 -0
- package/dist/rules/suppressions/biome/resolve.js +70 -0
- package/dist/rules/suppressions/biome/resolve.js.map +1 -0
- package/dist/rules/suppressions/biome/weakenings.d.ts +3 -3
- package/dist/rules/suppressions/biome/weakenings.d.ts.map +1 -1
- package/dist/rules/suppressions/biome/weakenings.js +41 -33
- package/dist/rules/suppressions/biome/weakenings.js.map +1 -1
- package/dist/rules/suppressions/biome-overrides.d.ts.map +1 -1
- package/dist/rules/suppressions/biome-overrides.js +31 -23
- package/dist/rules/suppressions/biome-overrides.js.map +1 -1
- package/package.json +5 -3
- package/src/rules/suppressions/biome/configs.ts +97 -28
- package/src/rules/suppressions/biome/declarations.ts +37 -0
- package/src/rules/suppressions/biome/resolve.ts +86 -0
- package/src/rules/suppressions/biome/weakenings.ts +54 -37
- package/src/rules/suppressions/biome-overrides.ts +40 -31
- package/tsconfig/base.json +35 -0
- package/tsconfig/package.json +10 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"weakenings.js","sourceRoot":"","sources":["../../../../src/rules/suppressions/biome/weakenings.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"weakenings.js","sourceRoot":"","sources":["../../../../src/rules/suppressions/biome/weakenings.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"biome-overrides.d.ts","sourceRoot":"","sources":["../../../src/rules/suppressions/biome-overrides.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"biome-overrides.d.ts","sourceRoot":"","sources":["../../../src/rules/suppressions/biome-overrides.ts"],"names":[],"mappings":"AAmDA,eAAO,MAAM,cAAc;;;;;;EAkBzB,CAAC"}
|
|
@@ -1,46 +1,54 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
2
|
import { defineRule } from "../../rule.js";
|
|
3
3
|
import { biomeConfigs } from "./biome/configs.js";
|
|
4
|
+
import { Declaration, matches, scoped, scopeText } from "./biome/declarations.js";
|
|
4
5
|
import { weakeningsOf } from "./biome/weakenings.js";
|
|
5
|
-
const Declaration = Schema.Struct({
|
|
6
|
-
rule: Schema.NonEmptyString,
|
|
7
|
-
includes: Schema.NonEmptyArray(Schema.NonEmptyString),
|
|
8
|
-
reason: Schema.String.check(Schema.isPattern(/\S/, { expected: "a reason that says why the scope needs the rule weakened" })),
|
|
9
|
-
});
|
|
10
6
|
const BiomeOverridesOptions = Schema.Struct({
|
|
11
7
|
declared: Schema.Array(Declaration).pipe(Schema.withDecodingDefaultKey(Effect.succeed([]))),
|
|
12
8
|
});
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const undeclared = (
|
|
17
|
-
file:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
})
|
|
9
|
+
const shippedBy = ({ base, layers }) => layers.flatMap(({ preset }) => preset === undefined
|
|
10
|
+
? []
|
|
11
|
+
: preset.declared.map(({ includes, rule }) => ({ includes: includes.map((glob) => scoped(base, glob)), rule, specifier: preset.specifier })));
|
|
12
|
+
const undeclared = ({ includes, layer, line, rule }) => layer.preset === undefined
|
|
13
|
+
? { file: layer.path, line, message: `Weakens "${rule}" for ${scopeText(includes)} without a declaration.`, subject: rule }
|
|
14
|
+
: {
|
|
15
|
+
file: layer.preset.file,
|
|
16
|
+
line: layer.preset.line,
|
|
17
|
+
message: `Extends "${layer.preset.specifier}", which weakens "${rule}" for ${scopeText(includes)} without a declaration.`,
|
|
18
|
+
subject: rule,
|
|
19
|
+
};
|
|
22
20
|
const unused = (file, declaration) => ({
|
|
23
21
|
file,
|
|
24
22
|
message: `Declares "${declaration.rule}" for ${scopeText(declaration.includes)}, which no Biome config weakens. Remove the declaration.`,
|
|
25
23
|
subject: declaration.rule,
|
|
26
24
|
});
|
|
27
|
-
const
|
|
25
|
+
const repeated = (file, declaration, shipped) => ({
|
|
26
|
+
file,
|
|
27
|
+
message: `Declares "${declaration.rule}" for ${scopeText(declaration.includes)}, which "${shipped.specifier}" already declares. Remove the declaration.`,
|
|
28
|
+
subject: declaration.rule,
|
|
29
|
+
});
|
|
30
|
+
const staleOrRepeated = (file, weakenings, shipped) => (declaration) => {
|
|
31
|
+
if (!weakenings.some((weakening) => matches(declaration, weakening))) {
|
|
32
|
+
return [unused(file, declaration)];
|
|
33
|
+
}
|
|
34
|
+
const already = shipped.find((candidate) => matches(declaration, candidate));
|
|
35
|
+
return already === undefined ? [] : [repeated(file, declaration, already)];
|
|
36
|
+
};
|
|
28
37
|
export const biomeOverrides = defineRule({
|
|
29
38
|
id: "suppressions/biome-overrides",
|
|
30
39
|
description: "A Biome setting that turns a check off or down, or keeps files out of it, is an exception for every file it covers. Fix the code and remove the setting, or declare its rule, includes and reason under this rule's declared option.",
|
|
31
40
|
options: Schema.toStandardSchemaV1(BiomeOverridesOptions, { parseOptions: { errors: "all", onExcessProperty: "error" } }),
|
|
32
41
|
registrable: false,
|
|
33
42
|
check: async (inputs) => {
|
|
34
|
-
const
|
|
43
|
+
const { chains, problems, root } = await biomeConfigs(inputs);
|
|
35
44
|
const { declared } = inputs.options;
|
|
36
|
-
const weakenings =
|
|
37
|
-
const
|
|
45
|
+
const weakenings = chains.flatMap(weakeningsOf);
|
|
46
|
+
const shipped = chains.flatMap(shippedBy);
|
|
47
|
+
const covered = (weakening) => [...declared, ...shipped].some((declaration) => matches(declaration, weakening));
|
|
38
48
|
return [
|
|
39
|
-
...
|
|
40
|
-
...weakenings.filter((weakening) => !
|
|
41
|
-
...declared
|
|
42
|
-
.filter((declaration) => !weakenings.some((weakening) => matches(declaration, weakening)))
|
|
43
|
-
.map((declaration) => unused(root, declaration)),
|
|
49
|
+
...problems,
|
|
50
|
+
...weakenings.filter((weakening) => !covered(weakening)).map(undeclared),
|
|
51
|
+
...declared.flatMap(staleOrRepeated(root, weakenings, shipped)),
|
|
44
52
|
];
|
|
45
53
|
},
|
|
46
54
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"biome-overrides.js","sourceRoot":"","sources":["../../../src/rules/suppressions/biome-overrides.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,UAAU,EAAgB,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"biome-overrides.js","sourceRoot":"","sources":["../../../src/rules/suppressions/biome-overrides.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/quality",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Repository quality gate with typed rules, a shrink-only baseline and a registry of reasoned exceptions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|
|
25
25
|
"src",
|
|
26
|
+
"tsconfig",
|
|
26
27
|
"CHANGELOG.md",
|
|
27
28
|
"README.md"
|
|
28
29
|
],
|
|
@@ -33,6 +34,8 @@
|
|
|
33
34
|
"import": "./dist/index.js",
|
|
34
35
|
"default": "./dist/index.js"
|
|
35
36
|
},
|
|
37
|
+
"./tsconfig/base.json": "./tsconfig/base.json",
|
|
38
|
+
"./tsconfig/package.json": "./tsconfig/package.json",
|
|
36
39
|
"./package.json": "./package.json"
|
|
37
40
|
},
|
|
38
41
|
"publishConfig": {
|
|
@@ -62,7 +65,6 @@
|
|
|
62
65
|
"check": "biome check .",
|
|
63
66
|
"test": "vitest run",
|
|
64
67
|
"typecheck": "tsc --noEmit",
|
|
65
|
-
"
|
|
66
|
-
"ready": "pnpm check && pnpm typecheck && pnpm typecheck:compat && pnpm test && pnpm build"
|
|
68
|
+
"ready": "pnpm check && pnpm typecheck && pnpm test && pnpm build"
|
|
67
69
|
}
|
|
68
70
|
}
|
|
@@ -1,50 +1,119 @@
|
|
|
1
1
|
import { posix } from "node:path";
|
|
2
|
-
import type { RuleInputs } from "../../../rule.ts";
|
|
3
|
-
import {
|
|
2
|
+
import type { Finding, RuleInputs } from "../../../rule.ts";
|
|
3
|
+
import { type Declaration, decodeShipped, PRESET_DECLARATIONS } from "./declarations.ts";
|
|
4
|
+
import { itemsOf, type Json, member, type Parsed, parseJsonc, textOf } from "./json.ts";
|
|
5
|
+
import { resolvePackage } from "./resolve.ts";
|
|
4
6
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
export interface Preset {
|
|
8
|
+
readonly specifier: string;
|
|
9
|
+
readonly file: string;
|
|
10
|
+
readonly line: number;
|
|
11
|
+
readonly declared: ReadonlyArray<Declaration>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface Layer {
|
|
15
|
+
readonly path: string;
|
|
16
|
+
readonly json: Json;
|
|
17
|
+
readonly preset: Preset | undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface Chain {
|
|
21
|
+
readonly base: string;
|
|
22
|
+
readonly layers: ReadonlyArray<Layer>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface Loaded {
|
|
26
|
+
readonly layers: ReadonlyArray<Layer>;
|
|
27
|
+
readonly problems: ReadonlyArray<Finding>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type ReadText = RuleInputs["readText"];
|
|
8
31
|
|
|
9
32
|
const ROOT_CONFIGS: ReadonlyArray<string> = ["biome.json", "biome.jsonc"];
|
|
10
33
|
|
|
11
34
|
const NESTED = /(?:^|\/)biome\.jsonc?$/;
|
|
12
35
|
|
|
36
|
+
const RELATIVE = /^\.\.?(?:\/|$)/;
|
|
37
|
+
|
|
13
38
|
const directoryOf = (path: string): string => (posix.dirname(path) === "." ? "" : posix.dirname(path));
|
|
14
39
|
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
return entries.filter((entry) => entry.startsWith(".")).map((entry) => posix.join(posix.dirname(path), entry));
|
|
40
|
+
const read = async (readText: ReadText, path: string): Promise<Parsed> => {
|
|
41
|
+
const text = await readText(path);
|
|
42
|
+
return text === undefined ? { _tag: "Unreadable", reason: "the file does not exist" } : parseJsonc(path, text);
|
|
19
43
|
};
|
|
20
44
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
45
|
+
const problem = (file: string, message: string, line?: number): Loaded => ({ layers: [], problems: [{ file, line, message }] });
|
|
46
|
+
|
|
47
|
+
const local = async (readText: ReadText, path: string): Promise<Loaded> => {
|
|
48
|
+
const parsed = await read(readText, path);
|
|
49
|
+
return parsed._tag === "Parsed"
|
|
50
|
+
? { layers: [{ json: parsed.json, path, preset: undefined }], problems: [] }
|
|
51
|
+
: problem(path, `Cannot read this Biome config: ${parsed.reason}.`);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const fromPackage = async (readText: ReadText, from: string, entry: Json, specifier: string): Promise<Loaded> => {
|
|
55
|
+
const target = await resolvePackage(readText, specifier);
|
|
56
|
+
if (target === undefined) {
|
|
57
|
+
return problem(from, `Cannot resolve the Biome config "${specifier}" from node_modules.`, entry.line);
|
|
29
58
|
}
|
|
30
|
-
const parsed =
|
|
59
|
+
const parsed = await read(readText, target);
|
|
31
60
|
if (parsed._tag === "Unreadable") {
|
|
32
|
-
return
|
|
61
|
+
return problem(from, `Cannot read the Biome config "${specifier}" at ${target}: ${parsed.reason}.`, entry.line);
|
|
33
62
|
}
|
|
34
|
-
const
|
|
35
|
-
|
|
63
|
+
const shipped = posix.join(posix.dirname(target), PRESET_DECLARATIONS);
|
|
64
|
+
const declared = await decodeShipped(await readText(shipped));
|
|
65
|
+
if (declared._tag === "Invalid") {
|
|
66
|
+
return problem(from, `Cannot read the declarations shipped with "${specifier}" at ${shipped}: ${declared.issues.join("; ")}.`, entry.line);
|
|
67
|
+
}
|
|
68
|
+
const preset = { declared: declared.value, file: from, line: entry.line, specifier };
|
|
69
|
+
return { layers: [{ json: parsed.json, path: target, preset }], problems: [] };
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const extended = async (readText: ReadText, from: string, entry: Json): Promise<Loaded> => {
|
|
73
|
+
const specifier = textOf(entry);
|
|
74
|
+
if (specifier === undefined || specifier === "//") {
|
|
75
|
+
return { layers: [], problems: [] };
|
|
76
|
+
}
|
|
77
|
+
if (RELATIVE.test(specifier)) {
|
|
78
|
+
return local(readText, posix.join(posix.dirname(from), specifier));
|
|
79
|
+
}
|
|
80
|
+
const relative = posix.normalize(specifier);
|
|
81
|
+
return (await readText(relative)) === undefined ? fromPackage(readText, from, entry, specifier) : local(readText, relative);
|
|
36
82
|
};
|
|
37
83
|
|
|
38
|
-
const
|
|
84
|
+
const extendsOf = (json: Json): ReadonlyArray<Json> => itemsOf(member(json, "extends"));
|
|
85
|
+
|
|
86
|
+
const chainOf = async (readText: ReadText, path: string): Promise<Loaded> => {
|
|
87
|
+
const own = await local(readText, path);
|
|
88
|
+
const [layer] = own.layers;
|
|
89
|
+
if (layer === undefined) {
|
|
90
|
+
return own;
|
|
91
|
+
}
|
|
92
|
+
const parents = await Promise.all(extendsOf(layer.json).map((entry) => extended(readText, path, entry)));
|
|
93
|
+
return { layers: [...parents.flatMap((parent) => parent.layers), layer], problems: parents.flatMap((parent) => parent.problems) };
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const rootConfig = async (readText: ReadText): Promise<string | undefined> => {
|
|
39
97
|
const present = await Promise.all(ROOT_CONFIGS.map(async (path) => ((await readText(path)) === undefined ? [] : [path])));
|
|
40
98
|
return present.flat()[0];
|
|
41
99
|
};
|
|
42
100
|
|
|
43
|
-
export
|
|
44
|
-
|
|
101
|
+
export interface BiomeConfigs {
|
|
102
|
+
readonly root: string;
|
|
103
|
+
readonly chains: ReadonlyArray<Chain>;
|
|
104
|
+
readonly problems: ReadonlyArray<Finding>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const biomeConfigs = async ({ files, readText }: RuleInputs): Promise<BiomeConfigs> => {
|
|
45
108
|
const root = await rootConfig(readText);
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
109
|
+
const rootChain = root === undefined ? { layers: [], problems: [] } : await chainOf(readText, root);
|
|
110
|
+
const loaded = new Set(rootChain.layers.map((layer) => layer.path));
|
|
111
|
+
const nested = files.filter((path) => NESTED.test(path) && path !== root && !loaded.has(path));
|
|
112
|
+
const below = await Promise.all(nested.map(async (path) => ({ base: directoryOf(path), ...(await chainOf(readText, path)) })));
|
|
113
|
+
const all = [{ base: "", ...rootChain }, ...below];
|
|
114
|
+
return {
|
|
115
|
+
chains: all.filter((chain) => chain.layers.length > 0).map(({ base, layers }) => ({ base, layers })),
|
|
116
|
+
problems: all.flatMap((chain) => chain.problems),
|
|
117
|
+
root: root ?? "biome.json",
|
|
118
|
+
};
|
|
50
119
|
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { type Decoded, decodeWith } from "../../../decoded.ts";
|
|
3
|
+
|
|
4
|
+
export const Declaration = Schema.Struct({
|
|
5
|
+
rule: Schema.NonEmptyString,
|
|
6
|
+
includes: Schema.NonEmptyArray(Schema.NonEmptyString),
|
|
7
|
+
reason: Schema.String.check(Schema.isPattern(/\S/, { expected: "a reason that says why the scope needs the rule weakened" })),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export type Declaration = typeof Declaration.Type;
|
|
11
|
+
|
|
12
|
+
export const PRESET_DECLARATIONS = "declarations.json";
|
|
13
|
+
|
|
14
|
+
const shipped = Schema.toStandardSchemaV1(Schema.fromJsonString(Schema.Array(Declaration)), {
|
|
15
|
+
parseOptions: { errors: "all", onExcessProperty: "error" },
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const decodeShipped = (text: string | undefined): Promise<Decoded<ReadonlyArray<Declaration>>> =>
|
|
19
|
+
text === undefined ? Promise.resolve({ _tag: "Valid", value: [] }) : decodeWith(shipped, text);
|
|
20
|
+
|
|
21
|
+
export interface Scoped {
|
|
22
|
+
readonly rule: string;
|
|
23
|
+
readonly includes: ReadonlyArray<string>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const sameScope = (left: ReadonlyArray<string>, right: ReadonlyArray<string>): boolean =>
|
|
27
|
+
new Set(left).size === new Set(right).size && left.every((glob) => right.includes(glob));
|
|
28
|
+
|
|
29
|
+
export const matches = (declaration: Scoped, weakening: Scoped): boolean =>
|
|
30
|
+
declaration.rule === weakening.rule && sameScope(declaration.includes, weakening.includes);
|
|
31
|
+
|
|
32
|
+
export const scoped = (base: string, glob: string): string => {
|
|
33
|
+
const [, negation = "", path = glob] = /^(!*)(.*)$/.exec(glob) ?? [];
|
|
34
|
+
return base === "" ? glob : `${negation}${base}/${path}`;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const scopeText = (includes: ReadonlyArray<string>): string => includes.map((glob) => `"${glob}"`).join(", ");
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { posix } from "node:path";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
import { decodeWith } from "../../../decoded.ts";
|
|
4
|
+
import type { RuleInputs } from "../../../rule.ts";
|
|
5
|
+
|
|
6
|
+
const Manifest = Schema.Struct({
|
|
7
|
+
name: Schema.optionalKey(Schema.String),
|
|
8
|
+
main: Schema.optionalKey(Schema.String),
|
|
9
|
+
exports: Schema.optionalKey(Schema.Unknown),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
type Manifest = typeof Manifest.Type;
|
|
13
|
+
|
|
14
|
+
const manifestSchema = Schema.toStandardSchemaV1(Schema.fromJsonString(Manifest));
|
|
15
|
+
|
|
16
|
+
const CONDITIONS: ReadonlySet<string> = new Set(["biome", "default"]);
|
|
17
|
+
|
|
18
|
+
const SPECIFIER = /^((?:@[^/]+\/)?[^/]+)(?:\/(.+))?$/;
|
|
19
|
+
|
|
20
|
+
const isRecord = (value: unknown): value is Readonly<Record<string, unknown>> => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21
|
+
|
|
22
|
+
const targetOf = (value: unknown, star: string | undefined): string | undefined => {
|
|
23
|
+
if (typeof value === "string") {
|
|
24
|
+
return star === undefined ? value : value.replaceAll("*", star);
|
|
25
|
+
}
|
|
26
|
+
if (Array.isArray(value)) {
|
|
27
|
+
return value.map((item: unknown) => targetOf(item, star)).find((target) => target !== undefined);
|
|
28
|
+
}
|
|
29
|
+
const condition = isRecord(value) ? Object.keys(value).find((key) => CONDITIONS.has(key)) : undefined;
|
|
30
|
+
return isRecord(value) && condition !== undefined ? targetOf(value[condition], star) : undefined;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const bySpecificity = (left: string, right: string): number => right.indexOf("*") - left.indexOf("*") || right.length - left.length;
|
|
34
|
+
|
|
35
|
+
const patternTarget = (exports: Readonly<Record<string, unknown>>, key: string): string | undefined => {
|
|
36
|
+
const pattern = Object.keys(exports)
|
|
37
|
+
.filter((candidate) => candidate.split("*").length === 2)
|
|
38
|
+
.sort(bySpecificity)
|
|
39
|
+
.find((candidate) => {
|
|
40
|
+
const [prefix = "", suffix = ""] = candidate.split("*");
|
|
41
|
+
return key.startsWith(prefix) && key !== prefix && key.endsWith(suffix) && key.length >= candidate.length;
|
|
42
|
+
});
|
|
43
|
+
const [prefix = "", suffix = ""] = pattern?.split("*") ?? [];
|
|
44
|
+
return pattern === undefined ? undefined : targetOf(exports[pattern], key.slice(prefix.length, key.length - suffix.length));
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const exported = (exports: unknown, subpath: string): string | undefined => {
|
|
48
|
+
const key = subpath === "" ? "." : `./${subpath}`;
|
|
49
|
+
if (!isRecord(exports)) {
|
|
50
|
+
return targetOf(exports, undefined);
|
|
51
|
+
}
|
|
52
|
+
return Object.hasOwn(exports, key) ? targetOf(exports[key], undefined) : (patternTarget(exports, key) ?? targetOf(exports, undefined));
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const inPackage = (directory: string, target: string | undefined): string | undefined =>
|
|
56
|
+
target?.startsWith("./") === true ? posix.join(directory, target) : undefined;
|
|
57
|
+
|
|
58
|
+
const manifestAt = async (readText: RuleInputs["readText"], path: string): Promise<Manifest | undefined> => {
|
|
59
|
+
const text = await readText(path);
|
|
60
|
+
const decoded = text === undefined ? undefined : await decodeWith(manifestSchema, text);
|
|
61
|
+
return decoded?._tag === "Valid" ? decoded.value : undefined;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const fromDependency = (directory: string, manifest: Manifest, subpath: string): string | undefined => {
|
|
65
|
+
if (manifest.exports !== undefined) {
|
|
66
|
+
return inPackage(directory, exported(manifest.exports, subpath));
|
|
67
|
+
}
|
|
68
|
+
if (subpath === "" && manifest.main !== undefined) {
|
|
69
|
+
return posix.join(directory, manifest.main);
|
|
70
|
+
}
|
|
71
|
+
return subpath === "" ? undefined : posix.join(directory, subpath);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const resolvePackage = async (readText: RuleInputs["readText"], specifier: string): Promise<string | undefined> => {
|
|
75
|
+
const [, name, subpath = ""] = SPECIFIER.exec(specifier) ?? [];
|
|
76
|
+
if (name === undefined) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
const own = await manifestAt(readText, "package.json");
|
|
80
|
+
if (own?.name === name && own.exports !== undefined && subpath !== "") {
|
|
81
|
+
return inPackage("", exported(own.exports, subpath));
|
|
82
|
+
}
|
|
83
|
+
const directory = posix.join("node_modules", name);
|
|
84
|
+
const manifest = await manifestAt(readText, posix.join(directory, "package.json"));
|
|
85
|
+
return manifest === undefined ? undefined : fromDependency(directory, manifest, subpath);
|
|
86
|
+
};
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
+
import type { Chain, Layer } from "./configs.ts";
|
|
2
|
+
import { scoped } from "./declarations.ts";
|
|
1
3
|
import { entriesOf, isFalse, itemsOf, type Json, member, textOf } from "./json.ts";
|
|
2
4
|
|
|
3
5
|
export interface Weakening {
|
|
4
6
|
readonly rule: string;
|
|
5
7
|
readonly includes: ReadonlyArray<string>;
|
|
6
|
-
readonly file: string;
|
|
7
8
|
readonly line: number;
|
|
9
|
+
readonly layer: Layer;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
interface Setting {
|
|
11
13
|
readonly rule: string;
|
|
12
14
|
readonly line: number;
|
|
15
|
+
readonly weak: boolean;
|
|
16
|
+
readonly wholeGroup: boolean;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
const LINT_WEAK = new Set(["off", "warn", "info"]);
|
|
@@ -18,39 +22,45 @@ const PRESETS = new Set(["recommended", "preset"]);
|
|
|
18
22
|
|
|
19
23
|
const levelOf = (node: Json): string | undefined => textOf(node) ?? textOf(member(node, "level"));
|
|
20
24
|
|
|
21
|
-
const at = (rule: string, node: Json | undefined): ReadonlyArray<Setting> =>
|
|
25
|
+
const at = (rule: string, node: Json | undefined, weak: boolean, wholeGroup = false): ReadonlyArray<Setting> =>
|
|
26
|
+
node === undefined ? [] : [{ line: node.line, rule, weak, wholeGroup }];
|
|
22
27
|
|
|
23
|
-
const
|
|
24
|
-
|
|
28
|
+
const enabled = (rule: string, section: Json | undefined): ReadonlyArray<Setting> =>
|
|
29
|
+
at(rule, member(section, "enabled"), isFalse(member(section, "enabled")));
|
|
25
30
|
|
|
26
|
-
const
|
|
27
|
-
...(
|
|
28
|
-
...(
|
|
31
|
+
const defaults = (rule: string, set: Json | undefined): ReadonlyArray<Setting> => [
|
|
32
|
+
...at(rule, member(set, "recommended"), isFalse(member(set, "recommended"))),
|
|
33
|
+
...at(rule, member(set, "preset"), textOf(member(set, "preset")) === "none"),
|
|
29
34
|
];
|
|
30
35
|
|
|
36
|
+
const ruleSettings = (group: string, weak: ReadonlySet<string>, rules: Json): ReadonlyArray<Setting> =>
|
|
37
|
+
entriesOf(rules)
|
|
38
|
+
.filter(([rule]) => !PRESETS.has(rule))
|
|
39
|
+
.flatMap(([rule, setting]) => {
|
|
40
|
+
const level = levelOf(setting);
|
|
41
|
+
return level === undefined ? [] : at(`${group}/${rule}`, setting, weak.has(level));
|
|
42
|
+
});
|
|
43
|
+
|
|
31
44
|
const groupSettings = (prefix: string, weak: ReadonlySet<string>, groups: Json | undefined): ReadonlyArray<Setting> =>
|
|
32
45
|
entriesOf(groups)
|
|
33
46
|
.filter(([group]) => !PRESETS.has(group))
|
|
34
|
-
.flatMap(([group, rules]) =>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
]);
|
|
47
|
+
.flatMap(([group, rules]) => {
|
|
48
|
+
const severity = textOf(rules);
|
|
49
|
+
return severity === undefined
|
|
50
|
+
? [...defaults(`${prefix}/${group}`, rules), ...ruleSettings(`${prefix}/${group}`, weak, rules)]
|
|
51
|
+
: at(`${prefix}/${group}`, rules, weak.has(severity), true);
|
|
52
|
+
});
|
|
41
53
|
|
|
42
54
|
const linter = (section: Json | undefined): ReadonlyArray<Setting> => [
|
|
43
|
-
...
|
|
44
|
-
...
|
|
55
|
+
...enabled("lint", section),
|
|
56
|
+
...defaults("lint/recommended", member(section, "rules")),
|
|
45
57
|
...groupSettings("lint", LINT_WEAK, member(section, "rules")),
|
|
46
|
-
...entriesOf(member(section, "domains"))
|
|
47
|
-
.filter(([, value]) => textOf(value) === "none")
|
|
48
|
-
.flatMap(([domain, value]) => at(`lint/domains/${domain}`, value)),
|
|
58
|
+
...entriesOf(member(section, "domains")).flatMap(([domain, value]) => at(`lint/domains/${domain}`, value, textOf(value) === "none")),
|
|
49
59
|
];
|
|
50
60
|
|
|
51
61
|
const assist = (section: Json | undefined): ReadonlyArray<Setting> => [
|
|
52
|
-
...
|
|
53
|
-
...
|
|
62
|
+
...enabled("assist", section),
|
|
63
|
+
...defaults("assist/recommended", member(section, "actions")),
|
|
54
64
|
...groupSettings("assist", ASSIST_WEAK, member(section, "actions")),
|
|
55
65
|
];
|
|
56
66
|
|
|
@@ -64,17 +74,15 @@ const TOOLS: ReadonlyArray<readonly [string, string]> = [
|
|
|
64
74
|
|
|
65
75
|
const SCOPED_SECTIONS: ReadonlyArray<readonly [string, string]> = [["files", "files"], ...TOOLS];
|
|
66
76
|
|
|
67
|
-
const
|
|
77
|
+
const settingsOf = (scope: Json): ReadonlyArray<Setting> => [
|
|
68
78
|
...linter(member(scope, "linter")),
|
|
69
79
|
...assist(member(scope, "assist")),
|
|
70
|
-
...
|
|
71
|
-
...LANGUAGES.flatMap((language) => TOOLS.flatMap(([key, name]) =>
|
|
80
|
+
...enabled("format", member(scope, "formatter")),
|
|
81
|
+
...LANGUAGES.flatMap((language) => TOOLS.flatMap(([key, name]) => enabled(`${language}/${name}`, member(member(scope, language), key)))),
|
|
72
82
|
];
|
|
73
83
|
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
return base === "" ? glob : `${negation}${base}/${path}`;
|
|
77
|
-
};
|
|
84
|
+
const replaces = (later: Setting, earlier: Setting): boolean =>
|
|
85
|
+
later.rule === earlier.rule || (later.wholeGroup && earlier.rule.startsWith(`${later.rule}/`));
|
|
78
86
|
|
|
79
87
|
const includesOf = (override: Json): ReadonlyArray<string> => {
|
|
80
88
|
const includes = itemsOf(member(override, "includes")).flatMap((item) => textOf(item) ?? []);
|
|
@@ -84,19 +92,28 @@ const includesOf = (override: Json): ReadonlyArray<string> => {
|
|
|
84
92
|
const narrowsScope = (globs: ReadonlyArray<string>): boolean =>
|
|
85
93
|
globs.length > 0 && (globs.some((glob) => glob.startsWith("!")) || !globs.includes("**"));
|
|
86
94
|
|
|
87
|
-
const narrowings = (config: Json,
|
|
95
|
+
const narrowings = (config: Json, base: string): ReadonlyArray<Omit<Weakening, "layer">> =>
|
|
88
96
|
SCOPED_SECTIONS.flatMap(([key, name]) => {
|
|
89
97
|
const list = member(member(config, key), "includes");
|
|
90
98
|
const globs = itemsOf(list).flatMap((item) => textOf(item) ?? []);
|
|
91
99
|
return list === undefined || !narrowsScope(globs)
|
|
92
100
|
? []
|
|
93
|
-
: [{
|
|
101
|
+
: [{ includes: globs.map((glob) => scoped(base, glob)), line: list.line, rule: `${name}/includes` }];
|
|
94
102
|
});
|
|
95
103
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
const layerWeakenings = (layer: Layer, later: ReadonlyArray<Layer>, base: string): ReadonlyArray<Weakening> => {
|
|
105
|
+
const overriding = later.flatMap((next) => settingsOf(next.json));
|
|
106
|
+
const top = settingsOf(layer.json).filter((setting) => setting.weak && !overriding.some((next) => replaces(next, setting)));
|
|
107
|
+
return [
|
|
108
|
+
...narrowings(layer.json, base),
|
|
109
|
+
...top.map(({ line, rule }) => ({ includes: [scoped(base, "**")], line, rule })),
|
|
110
|
+
...itemsOf(member(layer.json, "overrides")).flatMap((override) =>
|
|
111
|
+
settingsOf(override)
|
|
112
|
+
.filter((setting) => setting.weak)
|
|
113
|
+
.map(({ line, rule }) => ({ includes: includesOf(override).map((glob) => scoped(base, glob)), line, rule })),
|
|
114
|
+
),
|
|
115
|
+
].map((weakening) => ({ ...weakening, layer }));
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const weakeningsOf = ({ base, layers }: Chain): ReadonlyArray<Weakening> =>
|
|
119
|
+
layers.flatMap((layer, index) => layerWeakenings(layer, layers.slice(index + 1), base));
|