@apptoolkit/harness 1.0.1 → 1.0.3
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/README.md +48 -17
- package/package.json +24 -10
- package/LICENSE +0 -21
- package/harness.js +0 -104
- package/payload/HARNESS.md +0 -21
- package/payload/author-style/README.md +0 -56
- package/payload/author-style/biome-core.json +0 -57
- package/payload/author-style/biome-react.json +0 -9
- package/payload/author-style/biome-sql.json +0 -9
- package/payload/author-style/bun.lock +0 -37
- package/payload/author-style/check.llm.md +0 -17
- package/payload/author-style/check.test.llm.md +0 -17
- package/payload/author-style/check.test.ts +0 -238
- package/payload/author-style/check.ts +0 -181
- package/payload/author-style/member-spacing.test.ts +0 -172
- package/payload/author-style/member-spacing.ts +0 -106
- package/payload/author-style/package.json +0 -18
- package/payload/author-style/plugins/class-member-order.grit +0 -133
- package/payload/author-style/plugins/event-handler-names.grit +0 -110
- package/payload/author-style/plugins/explicit-arrow-body.grit +0 -15
- package/payload/author-style/plugins/explicit-source-constructs.grit +0 -10
- package/payload/author-style/plugins/react-arrow-body.grit +0 -85
- package/payload/author-style/plugins/sql-style.grit +0 -125
- package/payload/author-style/react-module.llm.md +0 -17
- package/payload/author-style/react-module.test.ts +0 -150
- package/payload/author-style/react-module.ts +0 -255
- package/payload/author-style/source-policy.llm.md +0 -17
- package/payload/author-style/source-policy.test.ts +0 -70
- package/payload/author-style/source-policy.ts +0 -215
- package/payload/author-style/spacing.llm.md +0 -17
- package/payload/author-style/spacing.test.ts +0 -174
- package/payload/author-style/spacing.ts +0 -211
- package/payload/harness/POLICY.md +0 -33
- package/payload/harness/PROJECT.md +0 -26
- package/payload/harness/REVIEW.md +0 -17
- package/payload/harness/TEMPLATES.md +0 -20
- package/payload/harness/WORKFLOWS.md +0 -65
- package/payload/harness/runtime/ZOD-LICENSE +0 -21
- package/payload/harness/runtime/harness.mjs +0 -104
- package/payload/harness/services/README.md +0 -26
- package/payload/harness/services/penpot.json +0 -7
- package/payload/harness/services/plane.json +0 -9
- package/payload/harness/workflows/W01.md +0 -13
- package/payload/harness/workflows/W02.md +0 -13
- package/payload/harness/workflows/W03.md +0 -13
- package/payload/harness/workflows/W04.md +0 -13
- package/payload/harness/workflows/W05.md +0 -13
- package/payload/harness/workflows/W06.md +0 -13
- package/payload/harness/workflows/W07.md +0 -13
- package/payload/harness/workflows/W08.md +0 -13
- package/payload/harness/workflows/W09.md +0 -13
- package/payload/harness/workflows/W10.md +0 -13
- package/payload/harness/workflows/W11.md +0 -13
- package/payload/harness/workflows/W12.md +0 -13
- package/payload/harness/workflows/W13.md +0 -13
- package/payload/harness/workflows/W14.md +0 -13
- package/payload/harness/workflows/W15.md +0 -13
- package/payload/harness/workflows/W16.md +0 -13
- package/payload/harness/workflows/W17.md +0 -13
- package/payload/harness/workflows/W18.md +0 -13
- package/payload/harness/workflows/W19.md +0 -13
- package/payload/harness/workflows/W20.md +0 -13
- package/payload/harness/workflows/W21.md +0 -15
- package/payload/harness/workflows/W22.md +0 -13
- package/payload/harness/workflows/W23.md +0 -21
- package/payload/harness/workflows/W24.md +0 -13
- package/payload/harness/workflows/W25.md +0 -13
- package/payload/harness/workflows/W26.md +0 -13
- package/payload/harness/workflows/W27.md +0 -13
- package/payload/harness/workflows/W28.md +0 -13
- package/payload/harness/workflows/W29.md +0 -13
- package/payload/harness/workflows/W30.md +0 -13
- package/payload/harness/workflows/W31.md +0 -13
- package/payload/harness/workflows/W32.md +0 -13
- package/payload/harness/workflows/W33.md +0 -13
- package/payload/harness/workflows/W34.md +0 -13
- package/payload/harness/workflows/W35.md +0 -13
- package/payload/harness/workflows/W36.md +0 -13
- package/payload/harness/workflows/W37.md +0 -13
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"root": true,
|
|
3
|
-
"plugins": [
|
|
4
|
-
"./plugins/class-member-order.grit",
|
|
5
|
-
"./plugins/explicit-arrow-body.grit",
|
|
6
|
-
"./plugins/explicit-source-constructs.grit"
|
|
7
|
-
],
|
|
8
|
-
"formatter": {
|
|
9
|
-
"indentStyle": "space",
|
|
10
|
-
"indentWidth": 2,
|
|
11
|
-
"lineEnding": "lf",
|
|
12
|
-
"lineWidth": 120
|
|
13
|
-
},
|
|
14
|
-
"javascript": {
|
|
15
|
-
"formatter": {
|
|
16
|
-
"quoteStyle": "single",
|
|
17
|
-
"jsxQuoteStyle": "double",
|
|
18
|
-
"quoteProperties": "asNeeded",
|
|
19
|
-
"trailingCommas": "all",
|
|
20
|
-
"semicolons": "always",
|
|
21
|
-
"arrowParentheses": "always",
|
|
22
|
-
"bracketSameLine": false,
|
|
23
|
-
"bracketSpacing": true
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
"assist": {
|
|
27
|
-
"enabled": false
|
|
28
|
-
},
|
|
29
|
-
"linter": {
|
|
30
|
-
"rules": {
|
|
31
|
-
"recommended": false,
|
|
32
|
-
"correctness": {
|
|
33
|
-
"noUnusedImports": "error",
|
|
34
|
-
"noUnusedPrivateClassMembers": "error",
|
|
35
|
-
"noUnusedVariables": "error"
|
|
36
|
-
},
|
|
37
|
-
"nursery": {
|
|
38
|
-
"noFloatingPromises": "error",
|
|
39
|
-
"noMisusedPromises": "error",
|
|
40
|
-
"noUnsafeTypeAssertion": "error"
|
|
41
|
-
},
|
|
42
|
-
"style": {
|
|
43
|
-
"noNonNullAssertion": "error",
|
|
44
|
-
"useBlockStatements": "error",
|
|
45
|
-
"useConsistentMemberAccessibility": {
|
|
46
|
-
"level": "error",
|
|
47
|
-
"options": { "accessibility": "explicit" }
|
|
48
|
-
},
|
|
49
|
-
"useImportType": "error"
|
|
50
|
-
},
|
|
51
|
-
"suspicious": {
|
|
52
|
-
"noExplicitAny": "error",
|
|
53
|
-
"noTsIgnore": "error"
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"lockfileVersion": 2,
|
|
3
|
-
"configVersion": 0,
|
|
4
|
-
"workspaces": {
|
|
5
|
-
"": {
|
|
6
|
-
"name": "harness-author-style",
|
|
7
|
-
"devDependencies": {
|
|
8
|
-
"@biomejs/biome": "2.5.15",
|
|
9
|
-
"typescript": "5.9.3",
|
|
10
|
-
"zod": "4.6.7",
|
|
11
|
-
},
|
|
12
|
-
},
|
|
13
|
-
},
|
|
14
|
-
"packages": {
|
|
15
|
-
"@biomejs/biome": ["@biomejs/biome@2.5.15", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.5.15", "@biomejs/cli-darwin-x64": "2.5.15", "@biomejs/cli-linux-arm64": "2.5.15", "@biomejs/cli-linux-arm64-musl": "2.5.15", "@biomejs/cli-linux-x64": "2.5.15", "@biomejs/cli-linux-x64-musl": "2.5.15", "@biomejs/cli-win32-arm64": "2.5.15", "@biomejs/cli-win32-x64": "2.5.15" }, "bin": { "biome": "bin/biome" } }, "sha512-WZTW4slm/pdkh92K6t/3aEN++44JD1PQ7squ7RCMLI1flHGl43DVOIGXvCjHiZgBMO1V4uYxoFNtqrIA4qGuIQ=="],
|
|
16
|
-
|
|
17
|
-
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.5.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BZVzFhJ/mUvTLMYbc9x6el0o2Uv5zP7bACYyhFb6fSc1giroXm7PL0a5KqMJ9F+/BnkYRjagD/ROSDVOZ6eapg=="],
|
|
18
|
-
|
|
19
|
-
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.5.15", "", { "os": "darwin", "cpu": "x64" }, "sha512-V5Kw63V+fVGNFhFrizDxMbGXAzZCh8kYzJpy3GI6gTapWDg7bZK9oYC9CbVgQSEdTnupz0U5Efz8Ev+5vdBBXw=="],
|
|
20
|
-
|
|
21
|
-
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.5.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-XaG7P7eeSLYETD3K9grfB0mQmpLQZjygdbsxekWBJAg4am79fGAtRpfRyNnTvXB2fZFNnJZG3nXm7HBat0+VUw=="],
|
|
22
|
-
|
|
23
|
-
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.5.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-tGzZUTcJCV7tj5Adh/Gn6nR4MycqA3iohq2LEcrarAgRBkU0h0OKd2UzCRkOsOYOuUGaUJb4YmqRqyy2feGGSw=="],
|
|
24
|
-
|
|
25
|
-
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.5.15", "", { "os": "linux", "cpu": "x64" }, "sha512-xE4iEW/3LqlYj9GFgGrFsFSH51dEEpUbYWBfeOv0q87WkUDxK2o/HhreSb7qMJqck70RVM6Lg96hgedcAYcOkA=="],
|
|
26
|
-
|
|
27
|
-
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.5.15", "", { "os": "linux", "cpu": "x64" }, "sha512-IlxUcyxilVGPsE008x13pWdkTbU3nQpP2i9b5UrbOYj6goBkKsRX1XB7yJLcV+O1H9LBchIIm4adOaVLZBX0ZQ=="],
|
|
28
|
-
|
|
29
|
-
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.5.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-2kPKzhNlm8C+Ru3GcO0Me2ODkCBLrVOUNuyi84RJyWDVKAA4+Kjj3jMjL938lF6BRfvltC4vB2nPY9SvN51Ovg=="],
|
|
30
|
-
|
|
31
|
-
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.5.15", "", { "os": "win32", "cpu": "x64" }, "sha512-yAzh4UqEImV6Hcy0zjUqNfJAsUhoD64FBQCdTG8Md/Z2wQeVP/ZmJ3XRYAs1g8J9fjTFyl2fCtc5FB5til47+g=="],
|
|
32
|
-
|
|
33
|
-
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
34
|
-
|
|
35
|
-
"zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
|
|
36
|
-
}
|
|
37
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# Author style checker map
|
|
2
|
-
|
|
3
|
-
- [Inputs and project authority](#inputs-and-project-authority)
|
|
4
|
-
- [Native and parser checks](#native-and-parser-checks)
|
|
5
|
-
- [Verification and recovery](#verification-and-recovery)
|
|
6
|
-
|
|
7
|
-
## Inputs and project authority
|
|
8
|
-
|
|
9
|
-
Start in `check.ts` at `checkFiles`. The request schema describes the project root, selected files, profile and mode. `targetRelativePath` establishes containment and `configuredBiome` detects local Biome configuration. See [install and run](README.md#install-and-run) for adoption, supported files and exclusions.
|
|
10
|
-
|
|
11
|
-
## Native and parser checks
|
|
12
|
-
|
|
13
|
-
The `configurations` map selects the isolated Biome configuration. The checker uses the locked package, clears `BIOME_BINARY`, and starts Bun without dotenv loading or automatic installs. It propagates the native status. `findSourcePolicyIssues`, `findSpacingIssues`, `findMemberSpacingIssues` and `findReactModuleIssues` inspect the selected source without writing. The [check matrix](README.md#deterministic-checks) lists supported patterns and separate review boundaries.
|
|
14
|
-
|
|
15
|
-
## Verification and recovery
|
|
16
|
-
|
|
17
|
-
`check.test.ts` covers installation layout, native and project authority checks, selected inputs and supplementary results. The individual parser test files cover exact constructs and boundaries. See [install and run](README.md#install-and-run) for the test command. Do not treat a skipped, failed or interrupted check as evidence that selected code passed.
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# Checker integration test map
|
|
2
|
-
|
|
3
|
-
- [Installed layout and source selection](#installed-layout-and-source-selection)
|
|
4
|
-
- [Native profiles and policy](#native-profiles-and-policy)
|
|
5
|
-
- [Failure paths](#failure-paths)
|
|
6
|
-
|
|
7
|
-
## Installed layout and source selection
|
|
8
|
-
|
|
9
|
-
`runs when the profile is installed under the adopting project llm directory` covers private configuration discovery. `uses the isolated formatter and linter on exact paths outside the harness without writing` covers exact file names, unselected files and byte preservation.
|
|
10
|
-
|
|
11
|
-
## Native profiles and policy
|
|
12
|
-
|
|
13
|
-
`enforces core types, explicit updates, braces, branches and class layout` exercises the real engine and a rejected environment override. `uses the pinned Biome launcher when the target dotenv defines another binary` covers Bun subprocess isolation. `uses separately selected React and tagged SQL checks` distinguishes profile findings from the common configuration.
|
|
14
|
-
|
|
15
|
-
## Failure paths
|
|
16
|
-
|
|
17
|
-
`fails a mixed selection when Biome cannot check an oversized file` protects against partial checks reported as success. `refuses existing configurations, generated files, symlinks and an empty file selection` checks scope and authority. Supplementary parser cases have their own focused test files.
|
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict';
|
|
2
|
-
import { spawnSync } from 'node:child_process';
|
|
3
|
-
import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
6
|
-
import { test } from 'node:test';
|
|
7
|
-
import { fileURLToPath } from 'node:url';
|
|
8
|
-
|
|
9
|
-
const checker = fileURLToPath(new URL('check.ts', import.meta.url));
|
|
10
|
-
|
|
11
|
-
function executeFrom(entry: string, root: string, args: string[], env = process.env) {
|
|
12
|
-
const result = spawnSync(process.execPath, [entry, '--root', root, ...args], { encoding: 'utf8', env });
|
|
13
|
-
|
|
14
|
-
if (result.error) {
|
|
15
|
-
throw result.error;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return result;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function execute(root: string, ...args: string[]) {
|
|
22
|
-
return executeFrom(checker, root, args);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
test('runs when the profile is installed under the adopting project llm directory', (context) => {
|
|
26
|
-
const root = mkdtempSync(join(tmpdir(), 'harness-style-'));
|
|
27
|
-
|
|
28
|
-
context.after(() => rmSync(root, { recursive: true, force: true }));
|
|
29
|
-
|
|
30
|
-
const profile = join(root, 'llm', 'author-style');
|
|
31
|
-
|
|
32
|
-
mkdirSync(profile, { recursive: true });
|
|
33
|
-
|
|
34
|
-
const packageDirectory = fileURLToPath(new URL('.', import.meta.url));
|
|
35
|
-
|
|
36
|
-
for (const file of [
|
|
37
|
-
'check.ts',
|
|
38
|
-
'source-policy.ts',
|
|
39
|
-
'spacing.ts',
|
|
40
|
-
'member-spacing.ts',
|
|
41
|
-
'react-module.ts',
|
|
42
|
-
'package.json',
|
|
43
|
-
'biome-core.json',
|
|
44
|
-
'plugins',
|
|
45
|
-
]) {
|
|
46
|
-
cpSync(join(packageDirectory, file), join(profile, file), { recursive: true });
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
symlinkSync(join(packageDirectory, 'node_modules'), join(profile, 'node_modules'), 'dir');
|
|
50
|
-
writeFileSync(join(root, 'entry.ts'), 'export const sample = true;\n');
|
|
51
|
-
|
|
52
|
-
const result = executeFrom(join(profile, 'check.ts'), root, ['entry.ts']);
|
|
53
|
-
|
|
54
|
-
assert.equal(result.status, 0, result.stderr);
|
|
55
|
-
assert.match(result.stdout, /Checked 1 file/);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('uses the isolated formatter and linter on exact paths outside the harness without writing', (context) => {
|
|
59
|
-
const root = mkdtempSync(join(tmpdir(), 'harness-style-'));
|
|
60
|
-
|
|
61
|
-
context.after(() => rmSync(root, { recursive: true, force: true }));
|
|
62
|
-
|
|
63
|
-
const clean = join(root, 'has spaces.ts');
|
|
64
|
-
const poorlyFormatted = "export const label='one'\n";
|
|
65
|
-
|
|
66
|
-
writeFileSync(clean, poorlyFormatted);
|
|
67
|
-
writeFileSync(join(root, 'unselected.ts'), 'export const counter: any = 0;\n');
|
|
68
|
-
|
|
69
|
-
const failure = execute(root, 'has spaces.ts');
|
|
70
|
-
|
|
71
|
-
assert.equal(failure.status, 1);
|
|
72
|
-
assert.match(failure.stderr, /Formatter would have printed/);
|
|
73
|
-
assert.equal(readFileSync(clean, 'utf8'), poorlyFormatted);
|
|
74
|
-
|
|
75
|
-
const lintOnly = execute(root, '--lint-only', 'has spaces.ts');
|
|
76
|
-
|
|
77
|
-
assert.equal(lintOnly.status, 0, lintOnly.stderr);
|
|
78
|
-
|
|
79
|
-
writeFileSync(clean, 'export const label = `line\n// @generated\nline`;\n');
|
|
80
|
-
|
|
81
|
-
const success = execute(root, clean);
|
|
82
|
-
|
|
83
|
-
assert.equal(success.status, 0, success.stderr);
|
|
84
|
-
assert.match(success.stdout, /Checked 1 file/);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test('enforces core types, explicit updates, braces, branches and class layout', (context) => {
|
|
88
|
-
const root = mkdtempSync(join(tmpdir(), 'harness-style-'));
|
|
89
|
-
|
|
90
|
-
context.after(() => rmSync(root, { recursive: true, force: true }));
|
|
91
|
-
|
|
92
|
-
writeFileSync(
|
|
93
|
-
join(root, 'shape.ts'),
|
|
94
|
-
[
|
|
95
|
-
'export class Selection {',
|
|
96
|
-
' public choose(flag: boolean): number {',
|
|
97
|
-
' let count = 1;',
|
|
98
|
-
' if (flag) count++;',
|
|
99
|
-
' return count;',
|
|
100
|
-
' }',
|
|
101
|
-
' public static create(): Selection {',
|
|
102
|
-
' return new Selection();',
|
|
103
|
-
' }',
|
|
104
|
-
'}',
|
|
105
|
-
'export const resolve = (value: boolean): string => (value ? "a" : "b");',
|
|
106
|
-
'export const loose: any = 1;',
|
|
107
|
-
'export const ambiguous: unknown = 2;',
|
|
108
|
-
].join('\n'),
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
const result = executeFrom(checker, root, ['--lint-only', 'shape.ts'], {
|
|
112
|
-
...process.env,
|
|
113
|
-
BIOME_BINARY: process.execPath,
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
assert.equal(result.status, 1);
|
|
117
|
-
assert.match(result.stderr, /Use an explicit update/);
|
|
118
|
-
assert.match(result.stderr, /useBlockStatements/);
|
|
119
|
-
assert.match(result.stderr, /explicit return/);
|
|
120
|
-
assert.match(result.stderr, /class member order/i);
|
|
121
|
-
assert.match(result.stderr, /noExplicitAny/);
|
|
122
|
-
assert.match(result.stderr, /Use an explicit domain type/);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test('uses the pinned Biome launcher when the target dotenv defines another binary', (context) => {
|
|
126
|
-
const root = mkdtempSync(join(tmpdir(), 'harness-style-'));
|
|
127
|
-
|
|
128
|
-
context.after(() => rmSync(root, { recursive: true, force: true }));
|
|
129
|
-
|
|
130
|
-
writeFileSync(join(root, '.env'), `BIOME_BINARY=${process.execPath}\n`);
|
|
131
|
-
writeFileSync(join(root, 'entry.ts'), 'export const sample = true;\n');
|
|
132
|
-
|
|
133
|
-
const result = execute(root, 'entry.ts');
|
|
134
|
-
|
|
135
|
-
assert.equal(result.status, 0, result.stderr);
|
|
136
|
-
assert.match(result.stdout, /Checked 1 file/);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test('uses separately selected React and tagged SQL checks', (context) => {
|
|
140
|
-
const root = mkdtempSync(join(tmpdir(), 'harness-style-'));
|
|
141
|
-
|
|
142
|
-
context.after(() => rmSync(root, { recursive: true, force: true }));
|
|
143
|
-
|
|
144
|
-
writeFileSync(join(root, 'Panel.tsx'), 'export default function Panel() { return <p>Hello</p>; }\n');
|
|
145
|
-
writeFileSync(join(root, 'Card.tsx'), 'const Card = () => <p>Hello</p>;\nexport default Card;\n');
|
|
146
|
-
writeFileSync(join(root, 'helper.ts'), 'export const UppercaseHelper = () => 1;\n');
|
|
147
|
-
writeFileSync(
|
|
148
|
-
join(root, 'query.ts'),
|
|
149
|
-
'export const find = (sql: (strings: TemplateStringsArray) => string) => sql`select id from records`;\n',
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
const core = execute(root, '--lint-only', 'Panel.tsx', 'query.ts');
|
|
153
|
-
|
|
154
|
-
assert.equal(core.status, 0, core.stderr);
|
|
155
|
-
|
|
156
|
-
const react = execute(root, '--profile', 'react', '--lint-only', 'Panel.tsx');
|
|
157
|
-
|
|
158
|
-
assert.equal(react.status, 1);
|
|
159
|
-
assert.match(react.stderr, /named const component/);
|
|
160
|
-
|
|
161
|
-
const arrow = execute(root, '--profile', 'react', '--lint-only', 'Card.tsx');
|
|
162
|
-
|
|
163
|
-
assert.equal(arrow.status, 1);
|
|
164
|
-
assert.match(arrow.stderr, /Annotate React arrow components/);
|
|
165
|
-
|
|
166
|
-
const helper = execute(root, '--profile', 'react', '--lint-only', 'helper.ts');
|
|
167
|
-
|
|
168
|
-
assert.equal(helper.status, 0, helper.stderr);
|
|
169
|
-
|
|
170
|
-
writeFileSync(
|
|
171
|
-
join(root, 'event.tsx'),
|
|
172
|
-
'export const view = <button onClick={() => { console.log("clicked"); }}>Go</button>;\n',
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
const plainEvent = execute(root, '--lint-only', 'event.tsx');
|
|
176
|
-
|
|
177
|
-
assert.equal(plainEvent.status, 0, plainEvent.stderr);
|
|
178
|
-
|
|
179
|
-
const reactEvent = execute(root, '--profile', 'react', '--lint-only', 'event.tsx');
|
|
180
|
-
|
|
181
|
-
assert.equal(reactEvent.status, 1);
|
|
182
|
-
assert.match(reactEvent.stderr, /inline JSX handler as one call expression/);
|
|
183
|
-
|
|
184
|
-
const sql = execute(root, '--profile', 'sql', '--lint-only', 'query.ts');
|
|
185
|
-
|
|
186
|
-
assert.equal(sql.status, 1);
|
|
187
|
-
assert.match(sql.stderr, /SQL keywords/i);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test('fails a mixed selection when Biome cannot check an oversized file', (context) => {
|
|
191
|
-
const root = mkdtempSync(join(tmpdir(), 'harness-style-'));
|
|
192
|
-
|
|
193
|
-
context.after(() => rmSync(root, { recursive: true, force: true }));
|
|
194
|
-
writeFileSync(join(root, 'small.ts'), 'export const sample = true;\n');
|
|
195
|
-
writeFileSync(join(root, 'large.ts'), `export const sample = '${'a'.repeat(1024 * 1024)}';\n`);
|
|
196
|
-
|
|
197
|
-
const result = execute(root, '--lint-only', 'small.ts', 'large.ts');
|
|
198
|
-
|
|
199
|
-
assert.equal(result.status, 1);
|
|
200
|
-
assert.match(result.stderr, /exceeds the configured maximum/);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
test('refuses existing configurations, generated files, symlinks and an empty file selection', (context) => {
|
|
204
|
-
const root = mkdtempSync(join(tmpdir(), 'harness-style-'));
|
|
205
|
-
|
|
206
|
-
context.after(() => rmSync(root, { recursive: true, force: true }));
|
|
207
|
-
|
|
208
|
-
mkdirSync(join(root, 'app'));
|
|
209
|
-
writeFileSync(join(root, 'app', 'biome.jsonc'), '{}\n');
|
|
210
|
-
writeFileSync(join(root, 'app', 'module.ts'), 'export const sample = true;\n');
|
|
211
|
-
writeFileSync(join(root, 'generated.ts'), '// @generated\nexport const sample = true;\n');
|
|
212
|
-
symlinkSync(join(root, 'generated.ts'), join(root, 'linked.ts'));
|
|
213
|
-
|
|
214
|
-
const expected = [
|
|
215
|
-
{ files: ['app/module.ts'], message: /project's configured Biome checks/ },
|
|
216
|
-
{ files: ['generated.ts'], message: /instead of a generated artifact/ },
|
|
217
|
-
{ files: ['linked.ts'], message: /instead of a symbolic link/ },
|
|
218
|
-
{ files: ['../outside.ts'], message: /inside/ },
|
|
219
|
-
{ files: [], message: /Supply the files you changed/ },
|
|
220
|
-
];
|
|
221
|
-
|
|
222
|
-
for (const { files, message } of expected) {
|
|
223
|
-
const result = execute(root, ...files);
|
|
224
|
-
|
|
225
|
-
assert.equal(result.status, 1);
|
|
226
|
-
assert.match(result.stderr, message);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const child = join(root, 'app', 'child');
|
|
230
|
-
|
|
231
|
-
mkdirSync(child);
|
|
232
|
-
writeFileSync(join(child, 'module.ts'), 'export const sample = true;\n');
|
|
233
|
-
|
|
234
|
-
const parentConfiguration = execute(child, 'module.ts');
|
|
235
|
-
|
|
236
|
-
assert.equal(parentConfiguration.status, 1);
|
|
237
|
-
assert.match(parentConfiguration.stderr, /project's configured Biome checks/);
|
|
238
|
-
});
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { lstatSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
3
|
-
import { createRequire } from 'node:module';
|
|
4
|
-
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { parseArgs } from 'node:util';
|
|
7
|
-
import { z } from 'zod';
|
|
8
|
-
import { findMemberSpacingIssues } from './member-spacing.ts';
|
|
9
|
-
import { findReactModuleIssues } from './react-module.ts';
|
|
10
|
-
import { findSourcePolicyIssues, hasGeneratedHeader } from './source-policy.ts';
|
|
11
|
-
import { findSpacingIssues } from './spacing.ts';
|
|
12
|
-
|
|
13
|
-
const requestSchema = z.object({
|
|
14
|
-
root: z.string().refine(isAbsolute, 'Supply the absolute project directory with --root.'),
|
|
15
|
-
profile: z.enum(['core', 'react', 'sql']),
|
|
16
|
-
lintOnly: z.boolean(),
|
|
17
|
-
files: z.array(z.string().min(1)).min(1, 'Supply the files you changed.'),
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
const sourceExtension = /\.(?:[cm]?ts|tsx)$/;
|
|
21
|
-
const protectedDirectories = new Set(['node_modules', '.git', '.next', 'coverage', 'dist', 'out']);
|
|
22
|
-
const nativeConfigurations = ['biome.json', 'biome.jsonc'];
|
|
23
|
-
const configurations = { core: 'biome-core.json', react: 'biome-react.json', sql: 'biome-sql.json' };
|
|
24
|
-
const requireFromProfile = createRequire(import.meta.url);
|
|
25
|
-
|
|
26
|
-
function targetRelativePath(root: string, file: string): string {
|
|
27
|
-
const path = relative(root, file);
|
|
28
|
-
|
|
29
|
-
if (!path || path === '..' || path.startsWith(`..${sep}`) || isAbsolute(path)) {
|
|
30
|
-
throw new Error(`Choose a source file inside ${root}: ${file}`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return path;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function configuredBiome(file: string): string | undefined {
|
|
37
|
-
let directory = dirname(file);
|
|
38
|
-
|
|
39
|
-
for (;;) {
|
|
40
|
-
for (const name of nativeConfigurations) {
|
|
41
|
-
const path = join(directory, name);
|
|
42
|
-
|
|
43
|
-
if (lstatSync(path, { throwIfNoEntry: false })) {
|
|
44
|
-
return path;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const parent = dirname(directory);
|
|
49
|
-
|
|
50
|
-
if (directory === parent) {
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
directory = parent;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function checkFiles(argumentsList: string[]): number {
|
|
59
|
-
const parsed = parseArgs({
|
|
60
|
-
args: argumentsList,
|
|
61
|
-
allowPositionals: true,
|
|
62
|
-
options: {
|
|
63
|
-
root: { type: 'string' },
|
|
64
|
-
profile: { type: 'string', default: 'core' },
|
|
65
|
-
'lint-only': { type: 'boolean', default: false },
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
const request = requestSchema.parse({
|
|
69
|
-
root: parsed.values.root,
|
|
70
|
-
profile: parsed.values.profile,
|
|
71
|
-
lintOnly: parsed.values['lint-only'],
|
|
72
|
-
files: parsed.positionals,
|
|
73
|
-
});
|
|
74
|
-
const requestedRoot = resolve(request.root);
|
|
75
|
-
const root = realpathSync(requestedRoot);
|
|
76
|
-
|
|
77
|
-
if (!statSync(root).isDirectory()) {
|
|
78
|
-
throw new Error(`The project root must be a directory: ${root}`);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const sources = new Map<string, string>();
|
|
82
|
-
|
|
83
|
-
for (const file of request.files) {
|
|
84
|
-
const requestedPath = resolve(requestedRoot, file);
|
|
85
|
-
let lexicalRoot = requestedRoot;
|
|
86
|
-
|
|
87
|
-
if (isAbsolute(file) && requestedPath.startsWith(`${root}${sep}`)) {
|
|
88
|
-
lexicalRoot = root;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const path = targetRelativePath(lexicalRoot, requestedPath);
|
|
92
|
-
const expectedPath = join(root, path);
|
|
93
|
-
const sourcePath = realpathSync(expectedPath);
|
|
94
|
-
|
|
95
|
-
if (expectedPath !== sourcePath) {
|
|
96
|
-
throw new Error(`Use the direct source path instead of a symbolic link: ${path}`);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (!sourceExtension.test(path) || !statSync(sourcePath).isFile()) {
|
|
100
|
-
throw new Error(`Choose an authored TypeScript or TSX file: ${path}`);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (path.split(sep).some((part) => protectedDirectories.has(part))) {
|
|
104
|
-
throw new Error(`Choose an authored file outside generated, dependency and Git directories: ${path}`);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const existingConfiguration = configuredBiome(sourcePath);
|
|
108
|
-
|
|
109
|
-
if (existingConfiguration) {
|
|
110
|
-
throw new Error(`Use the project's configured Biome checks for ${path}: ${existingConfiguration}`);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const source = readFileSync(sourcePath, 'utf8');
|
|
114
|
-
|
|
115
|
-
if (hasGeneratedHeader(source)) {
|
|
116
|
-
throw new Error(`Choose an authored source file instead of a generated artifact: ${path}`);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
sources.set(path, source);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const config = fileURLToPath(new URL(configurations[request.profile], import.meta.url));
|
|
123
|
-
const biome = requireFromProfile.resolve('@biomejs/biome/bin/biome');
|
|
124
|
-
const runtimeArguments = process.versions.bun ? ['--no-env-file', '--no-install'] : [];
|
|
125
|
-
const environment = { ...process.env };
|
|
126
|
-
|
|
127
|
-
delete environment.BIOME_BINARY;
|
|
128
|
-
|
|
129
|
-
const result = spawnSync(
|
|
130
|
-
process.execPath,
|
|
131
|
-
[
|
|
132
|
-
...runtimeArguments,
|
|
133
|
-
biome,
|
|
134
|
-
request.lintOnly ? 'lint' : 'check',
|
|
135
|
-
`--config-path=${config}`,
|
|
136
|
-
'--error-on-warnings',
|
|
137
|
-
'--',
|
|
138
|
-
...sources.keys(),
|
|
139
|
-
],
|
|
140
|
-
{ cwd: root, env: environment, stdio: 'inherit' },
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
if (result.error) {
|
|
144
|
-
throw new Error(`Cannot launch Biome in ${root}: ${result.error.message}`, { cause: result.error });
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (result.status === null) {
|
|
148
|
-
throw new Error(`Biome did not complete in ${root}${result.signal ? ` (${result.signal})` : ''}.`);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (result.status !== 0) {
|
|
152
|
-
return result.status;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
let count = 0;
|
|
156
|
-
|
|
157
|
-
for (const [path, source] of sources) {
|
|
158
|
-
const spacing = request.lintOnly
|
|
159
|
-
? []
|
|
160
|
-
: [...findSpacingIssues(source, path), ...findMemberSpacingIssues(source, path)];
|
|
161
|
-
const react = request.profile === 'react' ? findReactModuleIssues(source, path) : [];
|
|
162
|
-
|
|
163
|
-
if (readFileSync(join(root, path), 'utf8') !== source) {
|
|
164
|
-
throw new Error(`The source changed during verification. Run the check again: ${path}`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
for (const { line, column, message } of [...findSourcePolicyIssues(source, path), ...spacing, ...react]) {
|
|
168
|
-
process.stderr.write(`${path}:${line}:${column} ${message}\n`);
|
|
169
|
-
count += 1;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return count > 0 ? 1 : 0;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
process.exitCode = checkFiles(process.argv.slice(2));
|
|
178
|
-
} catch (error) {
|
|
179
|
-
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
180
|
-
process.exitCode = 1;
|
|
181
|
-
}
|