@shivaedev/quality 0.21.1 → 0.23.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 +22 -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/program.d.ts +9 -0
- package/dist/cli/program.d.ts.map +1 -0
- package/dist/cli/program.js +44 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/run-main.d.ts +5 -2
- package/dist/cli/run-main.d.ts.map +1 -1
- package/dist/cli/run-main.js +14 -10
- package/dist/cli/run-main.js.map +1 -1
- package/dist/cli.js +2 -46
- 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/built-in.d.ts +1 -1
- package/dist/rules/built-in.d.ts.map +1 -1
- package/dist/rules/built-in.js +4 -0
- package/dist/rules/built-in.js.map +1 -1
- package/dist/rules/maxDepth.d.ts +2 -0
- package/dist/rules/maxDepth.d.ts.map +1 -0
- package/dist/rules/maxDepth.js +20 -0
- package/dist/rules/maxDepth.js.map +1 -0
- package/dist/rules/noBarrel.d.ts +2 -0
- package/dist/rules/noBarrel.d.ts.map +1 -0
- package/dist/rules/noBarrel.js +14 -0
- package/dist/rules/noBarrel.js.map +1 -0
- 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 +19 -1
- package/docs/north-star.md +4 -4
- package/docs/roadmap.md +2 -2
- package/docs/rules.md +21 -34
- package/docs/tooling.md +2 -2
- package/package.json +2 -2
- 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/program.ts +50 -0
- package/src/cli/run-main.ts +17 -11
- package/src/cli.ts +2 -48
- 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/built-in.ts +4 -0
- package/src/rules/maxDepth.ts +24 -0
- package/src/rules/noBarrel.ts +16 -0
- package/src/rules/suppressions/biome/ConsumerDeclaration.ts +24 -0
- package/src/rules/suppressions/biome-overrides.ts +7 -26
|
@@ -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:
|
package/src/rules/built-in.ts
CHANGED
|
@@ -27,9 +27,13 @@ import { testsFollow } from "#rules/test-names/follow.ts";
|
|
|
27
27
|
import { biome } from "./biome.ts";
|
|
28
28
|
import { manifestsSorted } from "./manifests-sorted.ts";
|
|
29
29
|
import { maxLines } from "./max-lines.ts";
|
|
30
|
+
import { maxDepth } from "./maxDepth.ts";
|
|
31
|
+
import { noBarrel } from "./noBarrel.ts";
|
|
30
32
|
|
|
31
33
|
export const builtInRules = [
|
|
32
34
|
maxLines,
|
|
35
|
+
maxDepth,
|
|
36
|
+
noBarrel,
|
|
33
37
|
noJsdoc,
|
|
34
38
|
noLineReference,
|
|
35
39
|
noPrReference,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineRule, type Finding, type SourceFile } from "#rule.ts";
|
|
2
|
+
|
|
3
|
+
const DECLARATION = /\.d\.[cm]?ts$/u;
|
|
4
|
+
const TOO_DEEP = /^\t{8,}/u;
|
|
5
|
+
|
|
6
|
+
function findingsOf(file: SourceFile): readonly Finding[] {
|
|
7
|
+
return file.lines.flatMap((text, index) =>
|
|
8
|
+
TOO_DEEP.test(text)
|
|
9
|
+
? [
|
|
10
|
+
{
|
|
11
|
+
file: file.path,
|
|
12
|
+
line: index + 1,
|
|
13
|
+
message: "Indentation is 8+ tabs deep. Extract a named function or component.",
|
|
14
|
+
},
|
|
15
|
+
]
|
|
16
|
+
: [],
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const maxDepth = defineRule({
|
|
21
|
+
check: ({ sources }) => sources.filter((file) => !DECLARATION.test(file.path)).flatMap(findingsOf),
|
|
22
|
+
description: "Extract a named function or component before indentation reaches eight tabs.",
|
|
23
|
+
id: "nesting/max-depth",
|
|
24
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { posix } from "node:path";
|
|
2
|
+
import { defineRule } from "#rule.ts";
|
|
3
|
+
|
|
4
|
+
const BARREL_FILE = /^index\.tsx?$/u;
|
|
5
|
+
|
|
6
|
+
export const noBarrel = defineRule({
|
|
7
|
+
check: ({ sources }) =>
|
|
8
|
+
sources
|
|
9
|
+
.filter((file) => BARREL_FILE.test(posix.basename(file.path)))
|
|
10
|
+
.map((file) => ({
|
|
11
|
+
file: file.path,
|
|
12
|
+
message: "index.ts and index.tsx modules are banned. Name the module after its purpose and import it directly.",
|
|
13
|
+
})),
|
|
14
|
+
description: "Name TypeScript modules after their purpose instead of index.ts or index.tsx.",
|
|
15
|
+
id: "structure/no-barrel",
|
|
16
|
+
});
|
|
@@ -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
|
},
|