@apptoolkit/harness 0.0.0-stage → 1.0.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/README.md +57 -2
- package/dist/apptoolkit-harness-1.0.0.tgz +0 -0
- package/dist/bun/apptoolkit-harness-1.0.0.tgz +0 -0
- package/dist/bun/native/LICENSE +21 -0
- package/dist/bun/native/README.md +33 -0
- package/dist/bun/native/harness +4 -0
- package/dist/bun/native/payload/HARNESS.md +21 -0
- package/dist/bun/native/payload/author-style/README.md +56 -0
- package/dist/bun/native/payload/author-style/biome-core.json +57 -0
- package/dist/bun/native/payload/author-style/biome-react.json +9 -0
- package/dist/bun/native/payload/author-style/biome-sql.json +9 -0
- package/dist/bun/native/payload/author-style/bun.lock +37 -0
- package/dist/bun/native/payload/author-style/check.llm.md +17 -0
- package/dist/bun/native/payload/author-style/check.test.llm.md +17 -0
- package/dist/bun/native/payload/author-style/check.test.ts +238 -0
- package/dist/bun/native/payload/author-style/check.ts +181 -0
- package/dist/bun/native/payload/author-style/member-spacing.test.ts +172 -0
- package/dist/bun/native/payload/author-style/member-spacing.ts +106 -0
- package/dist/bun/native/payload/author-style/package.json +18 -0
- package/dist/bun/native/payload/author-style/plugins/class-member-order.grit +133 -0
- package/dist/bun/native/payload/author-style/plugins/event-handler-names.grit +110 -0
- package/dist/bun/native/payload/author-style/plugins/explicit-arrow-body.grit +15 -0
- package/dist/bun/native/payload/author-style/plugins/explicit-source-constructs.grit +10 -0
- package/dist/bun/native/payload/author-style/plugins/react-arrow-body.grit +85 -0
- package/dist/bun/native/payload/author-style/plugins/sql-style.grit +125 -0
- package/dist/bun/native/payload/author-style/react-module.llm.md +17 -0
- package/dist/bun/native/payload/author-style/react-module.test.ts +150 -0
- package/dist/bun/native/payload/author-style/react-module.ts +255 -0
- package/dist/bun/native/payload/author-style/source-policy.llm.md +17 -0
- package/dist/bun/native/payload/author-style/source-policy.test.ts +70 -0
- package/dist/bun/native/payload/author-style/source-policy.ts +215 -0
- package/dist/bun/native/payload/author-style/spacing.llm.md +17 -0
- package/dist/bun/native/payload/author-style/spacing.test.ts +174 -0
- package/dist/bun/native/payload/author-style/spacing.ts +211 -0
- package/dist/bun/native/payload/harness/POLICY.md +33 -0
- package/dist/bun/native/payload/harness/PROJECT.md +26 -0
- package/dist/bun/native/payload/harness/REVIEW.md +17 -0
- package/dist/bun/native/payload/harness/TEMPLATES.md +20 -0
- package/dist/bun/native/payload/harness/WORKFLOWS.md +65 -0
- package/dist/bun/native/payload/harness/runtime/ZOD-LICENSE +21 -0
- package/dist/bun/native/payload/harness/runtime/harness.mjs +104 -0
- package/dist/bun/native/payload/harness/services/README.md +26 -0
- package/dist/bun/native/payload/harness/services/penpot.json +7 -0
- package/dist/bun/native/payload/harness/services/plane.json +9 -0
- package/dist/bun/native/payload/harness/workflows/W01.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W02.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W03.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W04.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W05.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W06.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W07.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W08.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W09.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W10.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W11.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W12.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W13.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W14.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W15.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W16.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W17.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W18.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W19.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W20.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W21.md +15 -0
- package/dist/bun/native/payload/harness/workflows/W22.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W23.md +21 -0
- package/dist/bun/native/payload/harness/workflows/W24.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W25.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W26.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W27.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W28.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W29.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W30.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W31.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W32.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W33.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W34.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W35.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W36.md +13 -0
- package/dist/bun/native/payload/harness/workflows/W37.md +13 -0
- package/dist/bun/npm/LICENSE +21 -0
- package/dist/bun/npm/README.md +33 -0
- package/dist/bun/npm/harness.js +104 -0
- package/dist/bun/npm/package.json +23 -0
- package/dist/bun/npm/payload/HARNESS.md +21 -0
- package/dist/bun/npm/payload/author-style/README.md +56 -0
- package/dist/bun/npm/payload/author-style/biome-core.json +57 -0
- package/dist/bun/npm/payload/author-style/biome-react.json +9 -0
- package/dist/bun/npm/payload/author-style/biome-sql.json +9 -0
- package/dist/bun/npm/payload/author-style/bun.lock +37 -0
- package/dist/bun/npm/payload/author-style/check.llm.md +17 -0
- package/dist/bun/npm/payload/author-style/check.test.llm.md +17 -0
- package/dist/bun/npm/payload/author-style/check.test.ts +238 -0
- package/dist/bun/npm/payload/author-style/check.ts +181 -0
- package/dist/bun/npm/payload/author-style/member-spacing.test.ts +172 -0
- package/dist/bun/npm/payload/author-style/member-spacing.ts +106 -0
- package/dist/bun/npm/payload/author-style/package.json +18 -0
- package/dist/bun/npm/payload/author-style/plugins/class-member-order.grit +133 -0
- package/dist/bun/npm/payload/author-style/plugins/event-handler-names.grit +110 -0
- package/dist/bun/npm/payload/author-style/plugins/explicit-arrow-body.grit +15 -0
- package/dist/bun/npm/payload/author-style/plugins/explicit-source-constructs.grit +10 -0
- package/dist/bun/npm/payload/author-style/plugins/react-arrow-body.grit +85 -0
- package/dist/bun/npm/payload/author-style/plugins/sql-style.grit +125 -0
- package/dist/bun/npm/payload/author-style/react-module.llm.md +17 -0
- package/dist/bun/npm/payload/author-style/react-module.test.ts +150 -0
- package/dist/bun/npm/payload/author-style/react-module.ts +255 -0
- package/dist/bun/npm/payload/author-style/source-policy.llm.md +17 -0
- package/dist/bun/npm/payload/author-style/source-policy.test.ts +70 -0
- package/dist/bun/npm/payload/author-style/source-policy.ts +215 -0
- package/dist/bun/npm/payload/author-style/spacing.llm.md +17 -0
- package/dist/bun/npm/payload/author-style/spacing.test.ts +174 -0
- package/dist/bun/npm/payload/author-style/spacing.ts +211 -0
- package/dist/bun/npm/payload/harness/POLICY.md +33 -0
- package/dist/bun/npm/payload/harness/PROJECT.md +26 -0
- package/dist/bun/npm/payload/harness/REVIEW.md +17 -0
- package/dist/bun/npm/payload/harness/TEMPLATES.md +20 -0
- package/dist/bun/npm/payload/harness/WORKFLOWS.md +65 -0
- package/dist/bun/npm/payload/harness/runtime/ZOD-LICENSE +21 -0
- package/dist/bun/npm/payload/harness/runtime/harness.mjs +104 -0
- package/dist/bun/npm/payload/harness/services/README.md +26 -0
- package/dist/bun/npm/payload/harness/services/penpot.json +7 -0
- package/dist/bun/npm/payload/harness/services/plane.json +9 -0
- package/dist/bun/npm/payload/harness/workflows/W01.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W02.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W03.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W04.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W05.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W06.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W07.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W08.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W09.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W10.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W11.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W12.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W13.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W14.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W15.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W16.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W17.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W18.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W19.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W20.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W21.md +15 -0
- package/dist/bun/npm/payload/harness/workflows/W22.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W23.md +21 -0
- package/dist/bun/npm/payload/harness/workflows/W24.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W25.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W26.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W27.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W28.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W29.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W30.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W31.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W32.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W33.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W34.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W35.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W36.md +13 -0
- package/dist/bun/npm/payload/harness/workflows/W37.md +13 -0
- package/dist/npm/LICENSE +21 -0
- package/dist/npm/README.md +30 -0
- package/dist/npm/harness.js +104 -0
- package/dist/npm/package.json +25 -0
- package/dist/npm/payload/HARNESS.md +21 -0
- package/dist/npm/payload/author-style/README.md +56 -0
- package/dist/npm/payload/author-style/biome-core.json +57 -0
- package/dist/npm/payload/author-style/biome-react.json +9 -0
- package/dist/npm/payload/author-style/biome-sql.json +9 -0
- package/dist/npm/payload/author-style/check.llm.md +17 -0
- package/dist/npm/payload/author-style/check.test.llm.md +17 -0
- package/dist/npm/payload/author-style/check.test.ts +224 -0
- package/dist/npm/payload/author-style/check.ts +179 -0
- package/dist/npm/payload/author-style/member-spacing.test.ts +172 -0
- package/dist/npm/payload/author-style/member-spacing.ts +106 -0
- package/dist/npm/payload/author-style/package-lock.json +180 -0
- package/dist/npm/payload/author-style/package.json +17 -0
- package/dist/npm/payload/author-style/plugins/class-member-order.grit +133 -0
- package/dist/npm/payload/author-style/plugins/event-handler-names.grit +110 -0
- package/dist/npm/payload/author-style/plugins/explicit-arrow-body.grit +15 -0
- package/dist/npm/payload/author-style/plugins/explicit-source-constructs.grit +10 -0
- package/dist/npm/payload/author-style/plugins/react-arrow-body.grit +85 -0
- package/dist/npm/payload/author-style/plugins/sql-style.grit +125 -0
- package/dist/npm/payload/author-style/react-module.llm.md +17 -0
- package/dist/npm/payload/author-style/react-module.test.ts +150 -0
- package/dist/npm/payload/author-style/react-module.ts +255 -0
- package/dist/npm/payload/author-style/source-policy.llm.md +17 -0
- package/dist/npm/payload/author-style/source-policy.test.ts +70 -0
- package/dist/npm/payload/author-style/source-policy.ts +215 -0
- package/dist/npm/payload/author-style/spacing.llm.md +17 -0
- package/dist/npm/payload/author-style/spacing.test.ts +174 -0
- package/dist/npm/payload/author-style/spacing.ts +211 -0
- package/dist/npm/payload/harness/POLICY.md +33 -0
- package/dist/npm/payload/harness/PROJECT.md +26 -0
- package/dist/npm/payload/harness/REVIEW.md +17 -0
- package/dist/npm/payload/harness/TEMPLATES.md +20 -0
- package/dist/npm/payload/harness/WORKFLOWS.md +65 -0
- package/dist/npm/payload/harness/runtime/ZOD-LICENSE +21 -0
- package/dist/npm/payload/harness/runtime/harness.mjs +104 -0
- package/dist/npm/payload/harness/services/README.md +26 -0
- package/dist/npm/payload/harness/services/penpot.json +7 -0
- package/dist/npm/payload/harness/services/plane.json +9 -0
- package/dist/npm/payload/harness/workflows/W01.md +13 -0
- package/dist/npm/payload/harness/workflows/W02.md +13 -0
- package/dist/npm/payload/harness/workflows/W03.md +13 -0
- package/dist/npm/payload/harness/workflows/W04.md +13 -0
- package/dist/npm/payload/harness/workflows/W05.md +13 -0
- package/dist/npm/payload/harness/workflows/W06.md +13 -0
- package/dist/npm/payload/harness/workflows/W07.md +13 -0
- package/dist/npm/payload/harness/workflows/W08.md +13 -0
- package/dist/npm/payload/harness/workflows/W09.md +13 -0
- package/dist/npm/payload/harness/workflows/W10.md +13 -0
- package/dist/npm/payload/harness/workflows/W11.md +13 -0
- package/dist/npm/payload/harness/workflows/W12.md +13 -0
- package/dist/npm/payload/harness/workflows/W13.md +13 -0
- package/dist/npm/payload/harness/workflows/W14.md +13 -0
- package/dist/npm/payload/harness/workflows/W15.md +13 -0
- package/dist/npm/payload/harness/workflows/W16.md +13 -0
- package/dist/npm/payload/harness/workflows/W17.md +13 -0
- package/dist/npm/payload/harness/workflows/W18.md +13 -0
- package/dist/npm/payload/harness/workflows/W19.md +13 -0
- package/dist/npm/payload/harness/workflows/W20.md +13 -0
- package/dist/npm/payload/harness/workflows/W21.md +15 -0
- package/dist/npm/payload/harness/workflows/W22.md +13 -0
- package/dist/npm/payload/harness/workflows/W23.md +21 -0
- package/dist/npm/payload/harness/workflows/W24.md +13 -0
- package/dist/npm/payload/harness/workflows/W25.md +13 -0
- package/dist/npm/payload/harness/workflows/W26.md +13 -0
- package/dist/npm/payload/harness/workflows/W27.md +13 -0
- package/dist/npm/payload/harness/workflows/W28.md +13 -0
- package/dist/npm/payload/harness/workflows/W29.md +13 -0
- package/dist/npm/payload/harness/workflows/W30.md +13 -0
- package/dist/npm/payload/harness/workflows/W31.md +13 -0
- package/dist/npm/payload/harness/workflows/W32.md +13 -0
- package/dist/npm/payload/harness/workflows/W33.md +13 -0
- package/dist/npm/payload/harness/workflows/W34.md +13 -0
- package/dist/npm/payload/harness/workflows/W35.md +13 -0
- package/dist/npm/payload/harness/workflows/W36.md +13 -0
- package/dist/npm/payload/harness/workflows/W37.md +13 -0
- package/package.json +35 -4
|
@@ -0,0 +1,181 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { findMemberSpacingIssues } from './member-spacing.ts';
|
|
5
|
+
|
|
6
|
+
const memberMessage = 'Leave one blank line between class members.';
|
|
7
|
+
const overloadMessage = 'Keep adjacent signatures and the implementation of an overload together.';
|
|
8
|
+
|
|
9
|
+
function check(lines: readonly string[]) {
|
|
10
|
+
return findMemberSpacingIssues(lines.join('\n'), 'example.ts');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
test('requires one blank line between authored properties, methods and accessors', () => {
|
|
14
|
+
assert.deepEqual(
|
|
15
|
+
check([
|
|
16
|
+
'class Example {',
|
|
17
|
+
' public value = 1;',
|
|
18
|
+
' public other = 2;',
|
|
19
|
+
'',
|
|
20
|
+
'',
|
|
21
|
+
' public get label() { return this.value; }',
|
|
22
|
+
'',
|
|
23
|
+
' public set label(value: number) { this.value = value; }',
|
|
24
|
+
'}',
|
|
25
|
+
]),
|
|
26
|
+
[
|
|
27
|
+
{ line: 3, column: 3, message: memberMessage },
|
|
28
|
+
{ line: 6, column: 3, message: memberMessage },
|
|
29
|
+
],
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('groups same-name overloads and constructor overloads with their implementations', () => {
|
|
34
|
+
assert.deepEqual(
|
|
35
|
+
check([
|
|
36
|
+
'class Example {',
|
|
37
|
+
' public constructor(value: string);',
|
|
38
|
+
' public constructor(value: number);',
|
|
39
|
+
' public constructor(public value: string | number) {}',
|
|
40
|
+
'',
|
|
41
|
+
' public lookup(value: string): string;',
|
|
42
|
+
" public 'lookup'(value: number): number;",
|
|
43
|
+
" public ['lookup'](value: string | number): string | number { return value; }",
|
|
44
|
+
'',
|
|
45
|
+
' public static lookup(value: string): string;',
|
|
46
|
+
' public static lookup(value: number): number;',
|
|
47
|
+
' public static lookup(value: string | number): string | number { return value; }',
|
|
48
|
+
'}',
|
|
49
|
+
]),
|
|
50
|
+
[],
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('reports breaks within overloads and separates distinct overload groups', () => {
|
|
55
|
+
assert.deepEqual(
|
|
56
|
+
check([
|
|
57
|
+
'class Example {',
|
|
58
|
+
' public constructor(value: string);',
|
|
59
|
+
'',
|
|
60
|
+
' public constructor(public value: string) {}',
|
|
61
|
+
' public lookup(value: string): string;',
|
|
62
|
+
'',
|
|
63
|
+
' public lookup(value: number): number;',
|
|
64
|
+
' public lookup(value: string | number): string | number { return value; }',
|
|
65
|
+
'',
|
|
66
|
+
' public static lookup(value: string): string;',
|
|
67
|
+
'',
|
|
68
|
+
' public static lookup(value: string): string { return value; }',
|
|
69
|
+
'}',
|
|
70
|
+
]),
|
|
71
|
+
[
|
|
72
|
+
{ line: 4, column: 3, message: overloadMessage },
|
|
73
|
+
{ line: 5, column: 3, message: memberMessage },
|
|
74
|
+
{ line: 7, column: 3, message: overloadMessage },
|
|
75
|
+
{ line: 12, column: 3, message: overloadMessage },
|
|
76
|
+
],
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('keeps bodyless members separate if a matching adjacent member is absent', () => {
|
|
81
|
+
assert.deepEqual(
|
|
82
|
+
check([
|
|
83
|
+
'abstract class Example {',
|
|
84
|
+
' public abstract read(value: string): string;',
|
|
85
|
+
' public static read(value: string): string { return value; }',
|
|
86
|
+
' public abstract write(value: string): void;',
|
|
87
|
+
' public value = 1;',
|
|
88
|
+
'',
|
|
89
|
+
' public writeOther(): void {}',
|
|
90
|
+
'}',
|
|
91
|
+
]),
|
|
92
|
+
[
|
|
93
|
+
{ line: 3, column: 3, message: memberMessage },
|
|
94
|
+
{ line: 4, column: 3, message: memberMessage },
|
|
95
|
+
{ line: 5, column: 3, message: memberMessage },
|
|
96
|
+
],
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('groups overload declarations in ambient and abstract classes without requiring a body', () => {
|
|
101
|
+
assert.deepEqual(
|
|
102
|
+
check([
|
|
103
|
+
'declare class Ambient {',
|
|
104
|
+
' public lookup(value: string): string;',
|
|
105
|
+
' public lookup(value: number): number;',
|
|
106
|
+
'}',
|
|
107
|
+
'abstract class Example {',
|
|
108
|
+
' public abstract lookup(value: string): string;',
|
|
109
|
+
'',
|
|
110
|
+
' public abstract lookup(value: number): number;',
|
|
111
|
+
'}',
|
|
112
|
+
]),
|
|
113
|
+
[{ line: 8, column: 3, message: overloadMessage }],
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('matches overloads with the same computed or private name', () => {
|
|
118
|
+
assert.deepEqual(
|
|
119
|
+
check([
|
|
120
|
+
'class Example {',
|
|
121
|
+
' public [Symbol.iterator](value: string): string;',
|
|
122
|
+
' public [Symbol.iterator](value: string): string { return value; }',
|
|
123
|
+
'',
|
|
124
|
+
' #lookup(value: string): string;',
|
|
125
|
+
' #lookup(value: string): string { return value; }',
|
|
126
|
+
'}',
|
|
127
|
+
]),
|
|
128
|
+
[],
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('counts spacing outside comments and preserves comment ownership with CRLF', () => {
|
|
133
|
+
const source = [
|
|
134
|
+
'class Example {',
|
|
135
|
+
' public value = 1; /* trailing',
|
|
136
|
+
'',
|
|
137
|
+
' details */',
|
|
138
|
+
' /* leading',
|
|
139
|
+
'',
|
|
140
|
+
' details */',
|
|
141
|
+
' public next = 2;',
|
|
142
|
+
'',
|
|
143
|
+
' // Attached to the following member.',
|
|
144
|
+
' public final = 3;',
|
|
145
|
+
'}',
|
|
146
|
+
].join('\r\n');
|
|
147
|
+
|
|
148
|
+
assert.deepEqual(findMemberSpacingIssues(source, 'example.ts'), [{ line: 8, column: 3, message: memberMessage }]);
|
|
149
|
+
assert.deepEqual(findMemberSpacingIssues(source.replace('*/\r\n /*', '*/\r\n \t\r\n /*'), 'example.ts'), []);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('visits class expressions and nested classes in source order and ignores non-class members', () => {
|
|
153
|
+
const source = [
|
|
154
|
+
'interface Shape { x: number; y: number }',
|
|
155
|
+
'const shape = { x: 1, y: 2 };',
|
|
156
|
+
'const Example = class {',
|
|
157
|
+
' public nested = class {',
|
|
158
|
+
' public first = 1;',
|
|
159
|
+
' public second = 2;',
|
|
160
|
+
' };',
|
|
161
|
+
' static { run(); }',
|
|
162
|
+
'}',
|
|
163
|
+
'class Empty { ; }',
|
|
164
|
+
'class Single { public value = 1; }',
|
|
165
|
+
'export const view = <button>Save</button>;',
|
|
166
|
+
].join('\n');
|
|
167
|
+
|
|
168
|
+
assert.deepEqual(findMemberSpacingIssues(source, 'example.tsx'), [
|
|
169
|
+
{ line: 6, column: 5, message: memberMessage },
|
|
170
|
+
{ line: 8, column: 3, message: memberMessage },
|
|
171
|
+
]);
|
|
172
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
|
|
3
|
+
type MemberSpacingIssue = { line: number; column: number; message: string };
|
|
4
|
+
type OverloadMember = ts.ConstructorDeclaration | ts.MethodDeclaration;
|
|
5
|
+
|
|
6
|
+
function isOverloadMember(member: ts.ClassElement): member is OverloadMember {
|
|
7
|
+
return ts.isConstructorDeclaration(member) || ts.isMethodDeclaration(member);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function methodName(member: ts.MethodDeclaration, file: ts.SourceFile): string {
|
|
11
|
+
const name = member.name;
|
|
12
|
+
|
|
13
|
+
if (ts.isComputedPropertyName(name)) {
|
|
14
|
+
if (ts.isStringLiteral(name.expression) || ts.isNumericLiteral(name.expression)) {
|
|
15
|
+
return `literal:${name.expression.text}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return `computed:${name.getText(file)}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (ts.isPrivateIdentifier(name)) {
|
|
22
|
+
return `private:${name.text}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return `literal:${name.text}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function belongsToOverload(signature: OverloadMember, following: OverloadMember, file: ts.SourceFile): boolean {
|
|
29
|
+
if (ts.isConstructorDeclaration(signature) || ts.isConstructorDeclaration(following)) {
|
|
30
|
+
return ts.isConstructorDeclaration(signature) && ts.isConstructorDeclaration(following);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const signatureStatic = ts.getCombinedModifierFlags(signature) & ts.ModifierFlags.Static;
|
|
34
|
+
const followingStatic = ts.getCombinedModifierFlags(following) & ts.ModifierFlags.Static;
|
|
35
|
+
|
|
36
|
+
return signatureStatic === followingStatic && methodName(signature, file) === methodName(following, file);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function findGroupedSignatures(members: readonly ts.ClassElement[], file: ts.SourceFile): Set<ts.ClassElement> {
|
|
40
|
+
const signatures = new Set<ts.ClassElement>();
|
|
41
|
+
let following: OverloadMember | undefined;
|
|
42
|
+
|
|
43
|
+
for (const member of members.toReversed()) {
|
|
44
|
+
if (isOverloadMember(member)) {
|
|
45
|
+
if (!member.body && following && belongsToOverload(member, following, file)) {
|
|
46
|
+
signatures.add(member);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
following = member;
|
|
50
|
+
} else {
|
|
51
|
+
following = undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return signatures;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function countBlankLines(previous: ts.ClassElement, current: ts.ClassElement, file: ts.SourceFile): number {
|
|
59
|
+
const trailingComments = ts.getTrailingCommentRanges(file.text, previous.end);
|
|
60
|
+
const previousEnd = trailingComments?.at(-1)?.end ?? previous.end;
|
|
61
|
+
const leadingComments = ts.getLeadingCommentRanges(file.text, previousEnd);
|
|
62
|
+
const currentStart = leadingComments?.[0]?.pos ?? current.getStart(file);
|
|
63
|
+
const gap = file.text.slice(previousEnd, currentStart);
|
|
64
|
+
|
|
65
|
+
return gap
|
|
66
|
+
.split(/\r?\n/)
|
|
67
|
+
.slice(1, -1)
|
|
68
|
+
.filter((line) => /^[^\S\r\n]*$/.test(line)).length;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function findMemberSpacingIssues(source: string, fileName: string): readonly MemberSpacingIssue[] {
|
|
72
|
+
const file = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true);
|
|
73
|
+
const issues: MemberSpacingIssue[] = [];
|
|
74
|
+
|
|
75
|
+
function visit(node: ts.Node): void {
|
|
76
|
+
if (ts.isClassLike(node)) {
|
|
77
|
+
const members = node.members.filter((member) => !ts.isSemicolonClassElement(member));
|
|
78
|
+
const signatures = findGroupedSignatures(members, file);
|
|
79
|
+
let previous: ts.ClassElement | undefined;
|
|
80
|
+
|
|
81
|
+
for (const member of members) {
|
|
82
|
+
if (previous) {
|
|
83
|
+
const inOverloadGroup = signatures.has(previous);
|
|
84
|
+
const requiredBlankLines = inOverloadGroup ? 0 : 1;
|
|
85
|
+
|
|
86
|
+
if (countBlankLines(previous, member, file) !== requiredBlankLines) {
|
|
87
|
+
const { line, character } = file.getLineAndCharacterOfPosition(member.getStart(file));
|
|
88
|
+
const message = inOverloadGroup
|
|
89
|
+
? 'Keep adjacent signatures and the implementation of an overload together.'
|
|
90
|
+
: 'Leave one blank line between class members.';
|
|
91
|
+
|
|
92
|
+
issues.push({ line: line + 1, column: character + 1, message });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
previous = member;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
ts.forEachChild(node, visit);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
visit(file);
|
|
104
|
+
|
|
105
|
+
return issues.sort((left, right) => left.line - right.line || left.column - right.column);
|
|
106
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "harness-author-style",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"packageManager": "bun@1.4.2",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"check": "bun --no-env-file --no-install check.ts",
|
|
8
|
+
"test": "bun test"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"bun": ">=1.4.2"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@biomejs/biome": "2.5.15",
|
|
15
|
+
"typescript": "5.9.3",
|
|
16
|
+
"zod": "4.6.7"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
language js(typescript, jsx)
|
|
2
|
+
|
|
3
|
+
pattern property($modifiers) {
|
|
4
|
+
or {
|
|
5
|
+
JsPropertyClassMember(modifiers=$modifiers, name=$name),
|
|
6
|
+
TsPropertySignatureClassMember(modifiers=$modifiers, name=$name),
|
|
7
|
+
TsInitializedPropertySignatureClassMember(modifiers=$modifiers, name=$name)
|
|
8
|
+
} where { not $name <: JsPrivateClassMemberName() }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
pattern method($modifiers) {
|
|
12
|
+
or {
|
|
13
|
+
JsMethodClassMember(modifiers=$modifiers, name=$name),
|
|
14
|
+
JsGetterClassMember(modifiers=$modifiers, name=$name),
|
|
15
|
+
JsSetterClassMember(modifiers=$modifiers, name=$name),
|
|
16
|
+
TsMethodSignatureClassMember(modifiers=$modifiers, name=$name),
|
|
17
|
+
TsGetterSignatureClassMember(modifiers=$modifiers, name=$name),
|
|
18
|
+
TsSetterSignatureClassMember(modifiers=$modifiers, name=$name)
|
|
19
|
+
} where { not $name <: JsPrivateClassMemberName() }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
pattern internal() {
|
|
23
|
+
contains TsAccessibilityModifier() as $accessibility where { $accessibility <: r"(?:private|protected)" }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pattern private_static_property() {
|
|
27
|
+
property($modifiers) where { $modifiers <: contains JsStaticModifier(), $modifiers <: internal() }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
pattern public_static_property() {
|
|
31
|
+
property($modifiers) where { $modifiers <: contains JsStaticModifier(), not $modifiers <: internal() }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
pattern private_static_method() {
|
|
35
|
+
method($modifiers) where { $modifiers <: contains JsStaticModifier(), $modifiers <: internal() }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pattern public_static_method() {
|
|
39
|
+
method($modifiers) where { $modifiers <: contains JsStaticModifier(), not $modifiers <: internal() }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pattern constructor_member() {
|
|
43
|
+
or {
|
|
44
|
+
JsConstructorClassMember(),
|
|
45
|
+
TsConstructorSignatureClassMember()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
pattern private_property() {
|
|
50
|
+
property($modifiers) where { not $modifiers <: contains JsStaticModifier(), $modifiers <: internal() }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
pattern public_property() {
|
|
54
|
+
property($modifiers) where { not $modifiers <: contains JsStaticModifier(), not $modifiers <: internal() }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
pattern private_method() {
|
|
58
|
+
method($modifiers) where { not $modifiers <: contains JsStaticModifier(), $modifiers <: internal() }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
pattern public_method() {
|
|
62
|
+
method($modifiers) where { not $modifiers <: contains JsStaticModifier(), not $modifiers <: internal() }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
or {
|
|
66
|
+
property(),
|
|
67
|
+
method(),
|
|
68
|
+
constructor_member()
|
|
69
|
+
} as $second where {
|
|
70
|
+
$second <: after $first,
|
|
71
|
+
$first <: or {
|
|
72
|
+
property(),
|
|
73
|
+
method(),
|
|
74
|
+
constructor_member()
|
|
75
|
+
},
|
|
76
|
+
or {
|
|
77
|
+
and { $second <: private_static_property(), not $first <: private_static_property() },
|
|
78
|
+
and {
|
|
79
|
+
$second <: public_static_property(),
|
|
80
|
+
not $first <: or {
|
|
81
|
+
private_static_property(),
|
|
82
|
+
public_static_property()
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
and {
|
|
86
|
+
$second <: private_static_method(),
|
|
87
|
+
$first <: or {
|
|
88
|
+
public_static_method(),
|
|
89
|
+
constructor_member(),
|
|
90
|
+
private_property(),
|
|
91
|
+
public_property(),
|
|
92
|
+
private_method(),
|
|
93
|
+
public_method()
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
and {
|
|
97
|
+
$second <: public_static_method(),
|
|
98
|
+
$first <: or {
|
|
99
|
+
constructor_member(),
|
|
100
|
+
private_property(),
|
|
101
|
+
public_property(),
|
|
102
|
+
private_method(),
|
|
103
|
+
public_method()
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
and {
|
|
107
|
+
$second <: constructor_member(),
|
|
108
|
+
$first <: or {
|
|
109
|
+
private_property(),
|
|
110
|
+
public_property(),
|
|
111
|
+
private_method(),
|
|
112
|
+
public_method()
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
and {
|
|
116
|
+
$second <: private_property(),
|
|
117
|
+
$first <: or {
|
|
118
|
+
public_property(),
|
|
119
|
+
private_method(),
|
|
120
|
+
public_method()
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
and {
|
|
124
|
+
$second <: public_property(),
|
|
125
|
+
$first <: or {
|
|
126
|
+
private_method(),
|
|
127
|
+
public_method()
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
and { $second <: private_method(), $first <: public_method() }
|
|
131
|
+
},
|
|
132
|
+
register_diagnostic(span=$second, message="Follow the class member order. Move this member before the preceding group.", severity="error")
|
|
133
|
+
}
|