@shivaedev/quality 0.21.1 → 0.22.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 +16 -0
- package/README.md +24 -8
- package/dist/biome/exceptionConfig.d.ts +7 -0
- package/dist/biome/exceptionConfig.d.ts.map +1 -0
- package/dist/biome/exceptionConfig.js +54 -0
- package/dist/biome/exceptionConfig.js.map +1 -0
- package/dist/biome/report.d.ts +2 -1
- package/dist/biome/report.d.ts.map +1 -1
- package/dist/biome/report.js +5 -5
- package/dist/biome/report.js.map +1 -1
- package/dist/biome/run.d.ts +2 -1
- package/dist/biome/run.d.ts.map +1 -1
- package/dist/biome/run.js +5 -4
- package/dist/biome/run.js.map +1 -1
- package/dist/cli/args.d.ts +5 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +6 -27
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/fix.d.ts.map +1 -1
- package/dist/cli/fix.js +9 -9
- package/dist/cli/fix.js.map +1 -1
- package/dist/cli/lint.d.ts.map +1 -1
- package/dist/cli/lint.js +2 -1
- package/dist/cli/lint.js.map +1 -1
- package/dist/cli/messages.d.ts +3 -0
- package/dist/cli/messages.d.ts.map +1 -0
- package/dist/cli/messages.js +30 -0
- package/dist/cli/messages.js.map +1 -0
- package/dist/cli/migrateExceptions.d.ts +4 -0
- package/dist/cli/migrateExceptions.d.ts.map +1 -0
- package/dist/cli/migrateExceptions.js +78 -0
- package/dist/cli/migrateExceptions.js.map +1 -0
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -1
- package/dist/config/decode.d.ts +6 -0
- package/dist/config/decode.d.ts.map +1 -1
- package/dist/config/decode.js +2 -0
- package/dist/config/decode.js.map +1 -1
- package/dist/config/load.d.ts +4 -0
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +10 -1
- package/dist/config/load.js.map +1 -1
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +11 -1
- package/dist/config/resolve.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/engine/run-rules.d.ts +1 -0
- package/dist/engine/run-rules.d.ts.map +1 -1
- package/dist/engine/run-rules.js +19 -4
- package/dist/engine/run-rules.js.map +1 -1
- package/dist/engine/session.d.ts.map +1 -1
- package/dist/engine/session.js +11 -3
- package/dist/engine/session.js.map +1 -1
- package/dist/exceptions/biomeFindings.d.ts +4 -0
- package/dist/exceptions/biomeFindings.d.ts.map +1 -0
- package/dist/exceptions/biomeFindings.js +14 -0
- package/dist/exceptions/biomeFindings.js.map +1 -0
- package/dist/exceptions/declaration.d.ts +11 -0
- package/dist/exceptions/declaration.d.ts.map +1 -0
- package/dist/exceptions/declaration.js +24 -0
- package/dist/exceptions/declaration.js.map +1 -0
- package/dist/exceptions/migrateBiome.d.ts +3 -0
- package/dist/exceptions/migrateBiome.d.ts.map +1 -0
- package/dist/exceptions/migrateBiome.js +62 -0
- package/dist/exceptions/migrateBiome.js.map +1 -0
- package/dist/exceptions/migrateConfig.d.ts +3 -0
- package/dist/exceptions/migrateConfig.d.ts.map +1 -0
- package/dist/exceptions/migrateConfig.js +57 -0
- package/dist/exceptions/migrateConfig.js.map +1 -0
- package/dist/exceptions/registryEntries.d.ts +4 -0
- package/dist/exceptions/registryEntries.d.ts.map +1 -0
- package/dist/exceptions/registryEntries.js +6 -0
- package/dist/exceptions/registryEntries.js.map +1 -0
- package/dist/rule.d.ts +3 -0
- package/dist/rule.d.ts.map +1 -1
- package/dist/rule.js.map +1 -1
- package/dist/rules/biome.js +2 -2
- package/dist/rules/biome.js.map +1 -1
- package/dist/rules/suppressions/biome/ConsumerDeclaration.d.ts +7 -0
- package/dist/rules/suppressions/biome/ConsumerDeclaration.d.ts.map +1 -0
- package/dist/rules/suppressions/biome/ConsumerDeclaration.js +15 -0
- package/dist/rules/suppressions/biome/ConsumerDeclaration.js.map +1 -0
- package/dist/rules/suppressions/biome-overrides.d.ts.map +1 -1
- package/dist/rules/suppressions/biome-overrides.js +7 -16
- package/dist/rules/suppressions/biome-overrides.js.map +1 -1
- package/docs/debt-and-hooks.md +31 -11
- package/docs/modules.md +2 -0
- package/docs/north-star.md +4 -4
- package/docs/roadmap.md +1 -1
- package/docs/rules.md +12 -33
- package/docs/tooling.md +2 -2
- package/package.json +1 -1
- package/src/biome/exceptionConfig.ts +70 -0
- package/src/biome/report.ts +11 -5
- package/src/biome/run.ts +17 -10
- package/src/cli/args.ts +7 -29
- package/src/cli/fix.ts +14 -9
- package/src/cli/lint.ts +3 -1
- package/src/cli/messages.ts +31 -0
- package/src/cli/migrateExceptions.ts +87 -0
- package/src/cli.ts +3 -0
- package/src/config/decode.ts +2 -0
- package/src/config/load.ts +15 -1
- package/src/config/resolve.ts +12 -1
- package/src/config.ts +2 -0
- package/src/engine/run-rules.ts +21 -4
- package/src/engine/session.ts +11 -3
- package/src/exceptions/biomeFindings.ts +20 -0
- package/src/exceptions/declaration.ts +38 -0
- package/src/exceptions/migrateBiome.ts +78 -0
- package/src/exceptions/migrateConfig.ts +64 -0
- package/src/exceptions/registryEntries.ts +10 -0
- package/src/rule.ts +3 -0
- package/src/rules/biome.ts +2 -2
- package/src/rules/suppressions/biome/ConsumerDeclaration.ts +24 -0
- package/src/rules/suppressions/biome-overrides.ts +7 -26
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { type Decoded, decodeWith } from "#decoded.ts";
|
|
3
|
+
import { ConsumerDeclaration } from "#rules/suppressions/biome/ConsumerDeclaration.ts";
|
|
4
|
+
|
|
5
|
+
const FILE = /^(?!\.\/)(?!\/)(?!.*(?:^|\/)\.\.(?:\/|$))[^*!?[\]{}\\]+$/u;
|
|
6
|
+
|
|
7
|
+
export const ExceptionDeclaration = Schema.Struct({
|
|
8
|
+
files: Schema.NonEmptyArray(Schema.NonEmptyString),
|
|
9
|
+
reason: Schema.String.check(Schema.isPattern(/\S/u, { expected: "a reason that says why the exception is permanent" })),
|
|
10
|
+
rules: Schema.NonEmptyArray(Schema.NonEmptyString),
|
|
11
|
+
subject: Schema.optionalKey(Schema.NonEmptyString),
|
|
12
|
+
}).check(
|
|
13
|
+
Schema.makeFilter(
|
|
14
|
+
({ files, reason, rules, subject }) =>
|
|
15
|
+
rules.every((rule) => {
|
|
16
|
+
if (rule.startsWith("biome/")) {
|
|
17
|
+
return (
|
|
18
|
+
subject === undefined
|
|
19
|
+
&& (rule === "biome/files/includes" || files.every((file) => FILE.test(file)))
|
|
20
|
+
&& Schema.is(ConsumerDeclaration)({ includes: files, reason, rule: rule.slice(6) })
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
return files.every((file) => FILE.test(file));
|
|
24
|
+
}),
|
|
25
|
+
{
|
|
26
|
+
message:
|
|
27
|
+
"Exceptions name explicit repository files and individual rules. Biome rules use biome/lint/<group>/<rule> or biome/assist/<group>/<action>, without a subject. Only biome/files/includes accepts ** and named directory exclusions.",
|
|
28
|
+
},
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export type ExceptionDeclaration = typeof ExceptionDeclaration.Type;
|
|
33
|
+
|
|
34
|
+
const standard = Schema.toStandardSchemaV1(Schema.Array(ExceptionDeclaration), { parseOptions: { errors: "all", onExcessProperty: "error" } });
|
|
35
|
+
|
|
36
|
+
export function decodeExceptions(value: unknown): Promise<Decoded<readonly ExceptionDeclaration[]>> {
|
|
37
|
+
return decodeWith(standard, value);
|
|
38
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import type { Declaration } from "#rules/suppressions/biome/declarations.ts";
|
|
3
|
+
|
|
4
|
+
function nameOf(property: ts.ObjectLiteralElementLike): string | undefined {
|
|
5
|
+
return ts.isPropertyAssignment(property) && ts.isStringLiteral(property.name) ? property.name.text : undefined;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function strip(node: ts.Expression, paths: readonly (readonly string[])[]): ts.Expression {
|
|
9
|
+
if (!ts.isObjectLiteralExpression(node)) {
|
|
10
|
+
return node;
|
|
11
|
+
}
|
|
12
|
+
const properties = node.properties.flatMap((property) => {
|
|
13
|
+
const name = nameOf(property);
|
|
14
|
+
const matching = paths.filter((path) => path[0] === name);
|
|
15
|
+
if (!ts.isPropertyAssignment(property) || matching.length === 0) {
|
|
16
|
+
return [property];
|
|
17
|
+
}
|
|
18
|
+
if (matching.some((path) => path.length === 1)) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
const value = strip(
|
|
22
|
+
property.initializer,
|
|
23
|
+
matching.map((path) => path.slice(1)),
|
|
24
|
+
);
|
|
25
|
+
return ts.isObjectLiteralExpression(value) && value.properties.length === 0
|
|
26
|
+
? []
|
|
27
|
+
: [ts.factory.updatePropertyAssignment(property, property.name, value)];
|
|
28
|
+
});
|
|
29
|
+
return ts.factory.updateObjectLiteralExpression(node, properties);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function settingPath(rule: string): readonly string[] {
|
|
33
|
+
const [tool = "", group = "", name = ""] = rule.split("/");
|
|
34
|
+
return tool === "files" ? ["files", "includes"] : [tool === "lint" ? "linter" : "assist", tool === "lint" ? "rules" : "actions", group, name];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function migrateBiome(text: string, file: string, declarations: readonly Declaration[]): string {
|
|
38
|
+
const source = ts.parseJsonText(file, text);
|
|
39
|
+
const root = source.statements[0]?.expression;
|
|
40
|
+
if (ts.parseConfigFileTextToJson(file, text).error !== undefined || root === undefined || !ts.isObjectLiteralExpression(root)) {
|
|
41
|
+
throw new Error(`Cannot migrate invalid Biome config ${file}. Repair it, then run quality exceptions migrate again.`);
|
|
42
|
+
}
|
|
43
|
+
const globals = declarations.filter((entry) => entry.rule === "files/includes");
|
|
44
|
+
const stripped = strip(
|
|
45
|
+
root,
|
|
46
|
+
globals.map((entry) => settingPath(entry.rule)),
|
|
47
|
+
);
|
|
48
|
+
if (!ts.isObjectLiteralExpression(stripped)) {
|
|
49
|
+
throw new Error(`Cannot migrate ${file}.`);
|
|
50
|
+
}
|
|
51
|
+
const properties = stripped.properties.flatMap((property) => {
|
|
52
|
+
if (nameOf(property) !== "overrides" || !ts.isPropertyAssignment(property) || !ts.isArrayLiteralExpression(property.initializer)) {
|
|
53
|
+
return [property];
|
|
54
|
+
}
|
|
55
|
+
const overrides = property.initializer.elements.flatMap((override) => {
|
|
56
|
+
if (!ts.isObjectLiteralExpression(override)) {
|
|
57
|
+
return [override];
|
|
58
|
+
}
|
|
59
|
+
const includes = override.properties.find((item) => nameOf(item) === "includes");
|
|
60
|
+
const paths =
|
|
61
|
+
includes !== undefined && ts.isPropertyAssignment(includes) && ts.isArrayLiteralExpression(includes.initializer)
|
|
62
|
+
? includes.initializer.elements.filter(ts.isStringLiteral).map((item) => item.text)
|
|
63
|
+
: ["**"];
|
|
64
|
+
const matching = declarations.filter(
|
|
65
|
+
(entry) => entry.rule !== "files/includes" && entry.includes.length === paths.length && entry.includes.every((path) => paths.includes(path)),
|
|
66
|
+
);
|
|
67
|
+
const updated = strip(
|
|
68
|
+
override,
|
|
69
|
+
matching.map((entry) => settingPath(entry.rule)),
|
|
70
|
+
);
|
|
71
|
+
return ts.isObjectLiteralExpression(updated) && updated.properties.every((item) => nameOf(item) === "includes") ? [] : [updated];
|
|
72
|
+
});
|
|
73
|
+
return overrides.length === 0
|
|
74
|
+
? []
|
|
75
|
+
: [ts.factory.updatePropertyAssignment(property, property.name, ts.factory.updateArrayLiteralExpression(property.initializer, overrides))];
|
|
76
|
+
});
|
|
77
|
+
return `${ts.createPrinter().printNode(ts.EmitHint.Expression, ts.factory.updateObjectLiteralExpression(root, properties), source)}\n`;
|
|
78
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import type { ExceptionDeclaration } from "./declaration.ts";
|
|
3
|
+
|
|
4
|
+
function objectOf(expression: ts.Expression): ts.ObjectLiteralExpression | undefined {
|
|
5
|
+
if (ts.isObjectLiteralExpression(expression)) {
|
|
6
|
+
return expression;
|
|
7
|
+
}
|
|
8
|
+
if (ts.isCallExpression(expression) && expression.arguments[0] !== undefined) {
|
|
9
|
+
return objectOf(expression.arguments[0]);
|
|
10
|
+
}
|
|
11
|
+
return ts.isSatisfiesExpression(expression) || ts.isAsExpression(expression) || ts.isParenthesizedExpression(expression)
|
|
12
|
+
? objectOf(expression.expression)
|
|
13
|
+
: undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function nameOf(property: ts.ObjectLiteralElementLike): string | undefined {
|
|
17
|
+
const name = property.name;
|
|
18
|
+
if (name === undefined) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
if (ts.isComputedPropertyName(name)) {
|
|
22
|
+
if (ts.isStringLiteral(name.expression) || ts.isNoSubstitutionTemplateLiteral(name.expression) || ts.isNumericLiteral(name.expression)) {
|
|
23
|
+
return name.expression.text;
|
|
24
|
+
}
|
|
25
|
+
throw new Error("Cannot migrate a computed property automatically. Use literal property keys, then run quality exceptions migrate again.");
|
|
26
|
+
}
|
|
27
|
+
return ts.isStringLiteral(name) || ts.isIdentifier(name) || ts.isNumericLiteral(name) ? name.text : undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function migrateConfig(text: string | undefined, file: string, exceptions: readonly ExceptionDeclaration[], hasDeclarations: boolean): string {
|
|
31
|
+
if (text === undefined) {
|
|
32
|
+
return `export default { exceptions: ${JSON.stringify(exceptions, null, "\t")} };\n`;
|
|
33
|
+
}
|
|
34
|
+
const source = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
35
|
+
const exported = source.statements.find(ts.isExportAssignment);
|
|
36
|
+
const object = exported === undefined ? undefined : objectOf(exported.expression);
|
|
37
|
+
if (object === undefined || object.properties.some(ts.isSpreadAssignment)) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Cannot migrate ${file} automatically. Export an object or defineConfig({ ... }) without spreads, then run quality exceptions migrate again.`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
const properties = object.properties.flatMap((property) => {
|
|
43
|
+
const name = nameOf(property);
|
|
44
|
+
if (name === "registry" || name === "exceptions") {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
if (name !== "rules" || !hasDeclarations) {
|
|
48
|
+
return [{ name: name ?? "", text: property.getFullText(source).trim() }];
|
|
49
|
+
}
|
|
50
|
+
if (
|
|
51
|
+
!(ts.isPropertyAssignment(property) && ts.isObjectLiteralExpression(property.initializer))
|
|
52
|
+
|| property.initializer.properties.some(ts.isSpreadAssignment)
|
|
53
|
+
) {
|
|
54
|
+
throw new Error(`Inline the rules object in ${file}, then run quality exceptions migrate again.`);
|
|
55
|
+
}
|
|
56
|
+
const rules = property.initializer.properties.filter((rule) => nameOf(rule) !== "suppressions/biome-overrides");
|
|
57
|
+
return rules.length === 0 ? [] : [{ name: "rules", text: `rules: {\n${rules.map((rule) => rule.getFullText(source).trim()).join(",\n")}\n}` }];
|
|
58
|
+
});
|
|
59
|
+
const next = properties.map((property) => property.text);
|
|
60
|
+
const index = properties.findIndex((property) => property.name > "exceptions");
|
|
61
|
+
next.splice(index < 0 ? next.length : index, 0, `exceptions: ${JSON.stringify(exceptions, null, "\t")}`);
|
|
62
|
+
const replacement = `{\n${next.join(",\n")}\n}`;
|
|
63
|
+
return text.slice(0, object.getStart(source)) + replacement + text.slice(object.end);
|
|
64
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ExceptionDeclaration } from "./declaration.ts";
|
|
2
|
+
import type { RegistryEntry } from "./registry.ts";
|
|
3
|
+
|
|
4
|
+
export function registryEntries(declarations: readonly ExceptionDeclaration[]): readonly RegistryEntry[] {
|
|
5
|
+
return declarations.flatMap(({ files, reason, rules, subject }) =>
|
|
6
|
+
rules
|
|
7
|
+
.filter((rule) => !rule.startsWith("biome/"))
|
|
8
|
+
.flatMap((rule) => files.map((file) => ({ file, reason, rule, ...(subject === undefined ? {} : { subject }) }))),
|
|
9
|
+
);
|
|
10
|
+
}
|
package/src/rule.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Bivariant } from "@shivaedev/types/bivariant.ts";
|
|
2
|
+
import type { ExceptionDeclaration } from "#exceptions/declaration.ts";
|
|
2
3
|
import { describeIssue, type StandardSchemaV1 } from "./standard-schema.ts";
|
|
3
4
|
|
|
4
5
|
export interface Finding {
|
|
@@ -19,6 +20,8 @@ export interface SourceFile {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export interface RuleInputs {
|
|
23
|
+
readonly configFile?: string;
|
|
24
|
+
readonly exceptions?: readonly ExceptionDeclaration[];
|
|
22
25
|
readonly files: readonly string[];
|
|
23
26
|
readonly readText: (path: string) => Promise<string | undefined>;
|
|
24
27
|
readonly root: string;
|
package/src/rules/biome.ts
CHANGED
|
@@ -43,8 +43,8 @@ async function presetFinding(readText: RuleInputs["readText"]): Promise<readonly
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
const bridge = defineRule({
|
|
46
|
-
check: async ({ readText, root }) => {
|
|
47
|
-
const [preset, report] = await Promise.all([presetFinding(readText), biomeReport(root, ["check"])]);
|
|
46
|
+
check: async ({ exceptions, readText, root }) => {
|
|
47
|
+
const [preset, report] = await Promise.all([presetFinding(readText), biomeReport(root, ["check"], exceptions)]);
|
|
48
48
|
return [...preset, ...report.diagnostics.filter((diagnostic) => FAILING.has(diagnostic.severity)).map(findingOf)];
|
|
49
49
|
},
|
|
50
50
|
description:
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { Declaration } from "./declarations.ts";
|
|
3
|
+
|
|
4
|
+
const GLOB = /[*!?[\]{}]/u;
|
|
5
|
+
const DIRECTORY = /^\.?[^./\\*!?[\]{}]+$/u;
|
|
6
|
+
const INDIVIDUAL = /^(?:lint|assist)\/(?!domains\/)[a-zA-Z]+\/[a-zA-Z]+$/u;
|
|
7
|
+
|
|
8
|
+
function namedExclusion(pattern: string): boolean {
|
|
9
|
+
const segments = pattern.slice(pattern.startsWith("!**/") ? 4 : 1).split("/");
|
|
10
|
+
return pattern.startsWith("!") && DIRECTORY.test(segments.at(-1) ?? "") && segments.every((segment) => segment === "*" || DIRECTORY.test(segment));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const ConsumerDeclaration = Declaration.check(
|
|
14
|
+
Schema.makeFilter(
|
|
15
|
+
({ includes, rule }) =>
|
|
16
|
+
rule === "files/includes"
|
|
17
|
+
? includes.includes("**") && includes.length > 1 && includes.every((pattern) => pattern === "**" || namedExclusion(pattern))
|
|
18
|
+
: INDIVIDUAL.test(rule) && includes.every((path) => !GLOB.test(path) && /[^/]+\.[^/]+$/u.test(path)),
|
|
19
|
+
{
|
|
20
|
+
message:
|
|
21
|
+
"Declare one lint rule or assist action for explicit files, or files/includes with ** and named directory exclusions. Whole checks, groups and blanket scopes cannot be weakened.",
|
|
22
|
+
},
|
|
23
|
+
),
|
|
24
|
+
);
|
|
@@ -1,31 +1,11 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
|
+
import { biomeExceptionHint, missingBiomeFiles } from "#exceptions/biomeFindings.ts";
|
|
2
3
|
import { defineRule, type Finding } from "#rule.ts";
|
|
4
|
+
import { ConsumerDeclaration } from "#rules/suppressions/biome/ConsumerDeclaration.ts";
|
|
3
5
|
import { biomeConfigs, type Chain } from "#rules/suppressions/biome/configs.ts";
|
|
4
|
-
import {
|
|
6
|
+
import { matches, type Scoped, scoped, scopeText } from "#rules/suppressions/biome/declarations.ts";
|
|
5
7
|
import { type Weakening, weakeningsOf } from "#rules/suppressions/biome/weakenings.ts";
|
|
6
8
|
|
|
7
|
-
const GLOB = /[*!?[\]{}]/u;
|
|
8
|
-
const DIRECTORY = /^\.?[^./\\*!?[\]{}]+$/u;
|
|
9
|
-
const INDIVIDUAL = /^(?:lint|assist)\/(?!domains\/)[a-zA-Z]+\/[a-zA-Z]+$/u;
|
|
10
|
-
|
|
11
|
-
function namedExclusion(pattern: string): boolean {
|
|
12
|
-
const segments = pattern.slice(pattern.startsWith("!**/") ? 4 : 1).split("/");
|
|
13
|
-
return pattern.startsWith("!") && DIRECTORY.test(segments.at(-1) ?? "") && segments.every((segment) => segment === "*" || DIRECTORY.test(segment));
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const ConsumerDeclaration = Declaration.check(
|
|
17
|
-
Schema.makeFilter(
|
|
18
|
-
({ includes, rule }) =>
|
|
19
|
-
rule === "files/includes"
|
|
20
|
-
? includes.includes("**") && includes.length > 1 && includes.every((pattern) => pattern === "**" || namedExclusion(pattern))
|
|
21
|
-
: INDIVIDUAL.test(rule) && includes.every((path) => !GLOB.test(path) && /[^/]+\.[^/]+$/u.test(path)),
|
|
22
|
-
{
|
|
23
|
-
message:
|
|
24
|
-
"Declare one lint rule or assist action for explicit files, or files/includes with ** and named directory exclusions. Whole checks, groups and blanket scopes cannot be weakened.",
|
|
25
|
-
},
|
|
26
|
-
),
|
|
27
|
-
);
|
|
28
|
-
|
|
29
9
|
const BiomeOverridesOptions = Schema.Struct({
|
|
30
10
|
declared: Schema.Array(ConsumerDeclaration).pipe(Schema.withDecodingDefaultKey(Effect.succeed([]))),
|
|
31
11
|
});
|
|
@@ -41,12 +21,12 @@ const shippedBy = ({ base, layers }: Chain): readonly Shipped[] =>
|
|
|
41
21
|
: preset.declared.map(({ includes, rule }) => ({ includes: includes.map((glob) => scoped(base, glob)), rule, specifier: preset.specifier })),
|
|
42
22
|
);
|
|
43
23
|
|
|
44
|
-
const undeclared = ({ includes, layer, line, rule }: Weakening): Finding =>
|
|
24
|
+
const undeclared = ({ includes, layer, line, rule }: Weakening, configFile: string): Finding =>
|
|
45
25
|
layer.preset === undefined
|
|
46
26
|
? {
|
|
47
27
|
file: layer.path,
|
|
48
28
|
line,
|
|
49
|
-
message: `Weakens "${rule}" for ${scopeText(includes)} without a declaration. Repair the code and remove the weakening setting
|
|
29
|
+
message: `Weakens "${rule}" for ${scopeText(includes)} without a declaration. Repair the code and remove the weakening setting.${Schema.is(ConsumerDeclaration)({ includes, reason: "Permanent exception.", rule }) ? biomeExceptionHint(includes, rule, configFile) : ""}`,
|
|
50
30
|
subject: rule,
|
|
51
31
|
}
|
|
52
32
|
: {
|
|
@@ -85,7 +65,8 @@ export const biomeOverrides = defineRule({
|
|
|
85
65
|
const covered = (weakening: Weakening): boolean => [...declared, ...shipped].some((declaration) => matches(declaration, weakening));
|
|
86
66
|
return [
|
|
87
67
|
...problems,
|
|
88
|
-
...
|
|
68
|
+
...(await missingBiomeFiles(inputs)),
|
|
69
|
+
...weakenings.filter((weakening) => !covered(weakening)).map((weakening) => undeclared(weakening, inputs.configFile ?? "quality.config.ts")),
|
|
89
70
|
...declared.flatMap(staleOrRepeated(root, weakenings, shipped)),
|
|
90
71
|
];
|
|
91
72
|
},
|