@boyingliu01/xp-gate 0.31.1 → 0.31.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/adapter-common.sh +41 -3
- package/adapters/gate-3.sh +8 -1
- package/adapters/gate-8.sh +67 -6
- package/adapters/go.sh +18 -1
- package/adapters/iac.sh +18 -1
- package/adapters/powershell.sh +37 -1
- package/adapters/python.sh +18 -4
- package/gate-3.sh +254 -10
- package/gate-8.sh +67 -6
- package/hooks/adapter-common.sh +41 -3
- package/hooks/gate-3.sh +254 -10
- package/hooks/gate-8.sh +67 -6
- package/hooks/lib/Measure-PowerShellComplexity.ps1 +186 -0
- package/hooks/lib/coverage-total.sh +20 -5
- package/hooks/lib/gate-5c-alignment.sh +145 -0
- package/hooks/lib/jscpd-run.sh +40 -1
- package/hooks/lib/native-path.sh +106 -0
- package/hooks/lib/prepush-score.py +143 -0
- package/hooks/lib/sprint-gate-report.sh +23 -0
- package/hooks/lib/toolchain-path.sh +370 -0
- package/hooks/lib/validate-code-walkthrough.cjs +224 -23
- package/hooks/pre-commit +403 -165
- package/hooks/pre-push +192 -74
- package/hooks/sprint-gate.sh +14 -3
- package/lib/__tests__/check-alignment-cli.test.mjs +20 -3
- package/lib/__tests__/check-alignment-reasons.test.mjs +91 -0
- package/lib/__tests__/doctor-delphi.test.js +46 -0
- package/lib/__tests__/doctor-exec-retry.test.js +20 -10
- package/lib/__tests__/doctor-hook-signatures.test.js +65 -0
- package/lib/__tests__/doctor-path-equivalence.test.js +12 -1
- package/lib/__tests__/doctor.test.js +7 -1
- package/lib/__tests__/hook-health-path.test.js +9 -1
- package/lib/__tests__/issue-566-sync-hooks-backup.test.js +221 -0
- package/lib/__tests__/issue-583-hook-deploy-log.test.js +121 -0
- package/lib/__tests__/issue-586-principles-cmdline.test.js +183 -0
- package/lib/__tests__/issue-589-check-all-gate3.test.js +190 -0
- package/lib/__tests__/issue-590-archlint-vacuous-pass.test.js +138 -0
- package/lib/__tests__/shared-paths-lazy-home.test.js +67 -0
- package/lib/arch.js +261 -44
- package/lib/check-alignment-runner.cjs +23 -0
- package/lib/check.js +4 -1
- package/lib/doctor.js +194 -49
- package/lib/gate-runner.js +265 -58
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/principles.js +228 -6
- package/lib/shared-paths.js +53 -19
- package/mock-policy/AGENTS.md +3 -3
- package/mutation/AGENTS.md +3 -3
- package/mutation/__tests__/gremlins-runner.test.ts +3 -3
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/claude-code/skills/delphi-review/SKILL.md +50 -0
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/dsh/skills/delphi-review/SKILL.md +50 -0
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/dsh/src/delphi-config-env-diagnostics.test.ts +210 -0
- package/plugins/dsh/src/delphi-config.ts +151 -9
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/opencode/skills/delphi-review/SKILL.md +50 -0
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/qoder/skills/delphi-review/SKILL.md +50 -0
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
- package/principles/AGENTS.md +3 -3
- package/principles/__tests__/issue-587-java-member-count.test.ts +279 -0
- package/principles/__tests__/issue-588-lsp-java-false-positives.test.ts +213 -0
- package/principles/adapters/java.ts +127 -11
- package/principles/rules/__tests__/solid/issue-588-lsp-base-contract.test.ts +252 -0
- package/principles/rules/__tests__/solid/lsp.test.ts +26 -22
- package/principles/rules/solid/lsp.ts +348 -23
- package/scripts/__tests__/sync-package-content.test.js +32 -1
- package/scripts/sync-package-content.js +6 -1
- package/skills/delphi-review/AGENTS.md +3 -3
- package/skills/delphi-review/SKILL.md +50 -0
- package/skills/sprint-flow/AGENTS.md +3 -3
- package/skills/test-specification-alignment/AGENTS.md +3 -3
- package/sprint-gate.sh +14 -3
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @test REQ-618
|
|
3
|
+
* @intent The LSP rule must still DETECT a real violation when the base contract is
|
|
4
|
+
* available in the same file, so that #588's "stay silent when undetermined" fix
|
|
5
|
+
* does not degenerate into "never report anything".
|
|
6
|
+
* @covers AC-618-04, AC-618-05
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect } from 'vitest';
|
|
9
|
+
import { lspRule } from '../../solid/lsp';
|
|
10
|
+
|
|
11
|
+
/** Adapter stub exposing exactly what the rule consumes. */
|
|
12
|
+
function adapterFor(classes: Array<{ name?: string; line?: number; code?: string }>) {
|
|
13
|
+
return {
|
|
14
|
+
detectLanguage: () => 'java',
|
|
15
|
+
parseAST: () => undefined,
|
|
16
|
+
extractFunctions: () => [],
|
|
17
|
+
extractClasses: () => classes,
|
|
18
|
+
countLines: () => 0,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A base class and a derived class in ONE file.
|
|
24
|
+
*
|
|
25
|
+
* `handle(DataSource ds)` is the base contract; the derived class may honour it or break
|
|
26
|
+
* it. Both classes are visible, so the rule has everything it needs to decide — which is
|
|
27
|
+
* exactly the case #588's fix must NOT silence.
|
|
28
|
+
*/
|
|
29
|
+
const BASE_WITH_ONE_PARAM = `
|
|
30
|
+
class BaseHandler {
|
|
31
|
+
public void handle(DataSource ds) { }
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
/** Line where the derived class starts in the two-class fixtures below. */
|
|
36
|
+
const DERIVED_CLASS_LINE = 4;
|
|
37
|
+
|
|
38
|
+
/** Line where the derived TypeScript class starts in the two-line-header fixtures. */
|
|
39
|
+
const TS_DERIVED_CLASS_LINE = 3;
|
|
40
|
+
|
|
41
|
+
describe('solid.lsp detects a violation when the base contract IS available', () => {
|
|
42
|
+
it('reports a derived parameter type the base method does not accept', () => {
|
|
43
|
+
const violations = lspRule.check('Handlers.java', adapterFor([
|
|
44
|
+
{ name: 'BaseHandler', line: 1, code: BASE_WITH_ONE_PARAM },
|
|
45
|
+
{
|
|
46
|
+
name: 'CsvHandler',
|
|
47
|
+
line: DERIVED_CLASS_LINE,
|
|
48
|
+
code: `class CsvHandler extends BaseHandler {
|
|
49
|
+
public void handle(FileChannel channel) { }
|
|
50
|
+
}`,
|
|
51
|
+
},
|
|
52
|
+
]) as never);
|
|
53
|
+
|
|
54
|
+
// The point of the whole rule: an override that narrows or changes the parameter type
|
|
55
|
+
// breaks substitutability, and here both sides are visible so the claim is earned.
|
|
56
|
+
expect(violations.length).toBeGreaterThan(0);
|
|
57
|
+
expect(violations[0].ruleId).toBe('solid.lsp');
|
|
58
|
+
expect(violations[0].message).toContain('FileChannel');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('stays silent when the override keeps the base parameter type', () => {
|
|
62
|
+
const violations = lspRule.check('Handlers.java', adapterFor([
|
|
63
|
+
{ name: 'BaseHandler', line: 1, code: BASE_WITH_ONE_PARAM },
|
|
64
|
+
{
|
|
65
|
+
name: 'CsvHandler',
|
|
66
|
+
line: DERIVED_CLASS_LINE,
|
|
67
|
+
code: `class CsvHandler extends BaseHandler {
|
|
68
|
+
public void handle(DataSource ds) { }
|
|
69
|
+
}`,
|
|
70
|
+
},
|
|
71
|
+
]) as never);
|
|
72
|
+
|
|
73
|
+
expect(violations).toHaveLength(0);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('stays silent when the base class is NOT in the file', () => {
|
|
77
|
+
// The control for the two cases above: same derived code, no base declaration
|
|
78
|
+
// available. Without this, a rule that simply reported every non-primitive parameter
|
|
79
|
+
// would pass the first case and fail only by accident elsewhere.
|
|
80
|
+
const violations = lspRule.check('Handlers.java', adapterFor([
|
|
81
|
+
{
|
|
82
|
+
name: 'CsvHandler',
|
|
83
|
+
line: 1,
|
|
84
|
+
code: `class CsvHandler extends BaseHandler {
|
|
85
|
+
public void handle(FileChannel channel) { }
|
|
86
|
+
}`,
|
|
87
|
+
},
|
|
88
|
+
]) as never);
|
|
89
|
+
|
|
90
|
+
expect(violations).toHaveLength(0);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('stays silent when the base method itself is not declared', () => {
|
|
94
|
+
// Base visible, but it does not declare `handle` at all — an inherited method from
|
|
95
|
+
// further up, which this analysis cannot see. Not a violation, not verifiable.
|
|
96
|
+
const baseWithoutHandle = `class BaseHandler { public void close() { } }`;
|
|
97
|
+
const violations = lspRule.check('Handlers.java', adapterFor([
|
|
98
|
+
{ name: 'BaseHandler', line: 1, code: baseWithoutHandle },
|
|
99
|
+
{
|
|
100
|
+
name: 'CsvHandler',
|
|
101
|
+
line: 2,
|
|
102
|
+
code: `class CsvHandler extends BaseHandler {
|
|
103
|
+
public void handle(FileChannel channel) { }
|
|
104
|
+
}`,
|
|
105
|
+
},
|
|
106
|
+
]) as never);
|
|
107
|
+
|
|
108
|
+
expect(violations).toHaveLength(0);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('resolves a base class referenced through a qualified name', () => {
|
|
112
|
+
// `extends com.acme.BaseHandler` is ordinary Java; a plain string compare would miss
|
|
113
|
+
// the base and silently downgrade a decidable case to silence.
|
|
114
|
+
const violations = lspRule.check('Handlers.java', adapterFor([
|
|
115
|
+
{ name: 'BaseHandler', line: 1, code: BASE_WITH_ONE_PARAM },
|
|
116
|
+
{
|
|
117
|
+
name: 'CsvHandler',
|
|
118
|
+
line: 2,
|
|
119
|
+
code: `class CsvHandler extends com.acme.BaseHandler {
|
|
120
|
+
public void handle(FileChannel channel) { }
|
|
121
|
+
}`,
|
|
122
|
+
},
|
|
123
|
+
]) as never);
|
|
124
|
+
|
|
125
|
+
expect(violations.length).toBeGreaterThan(0);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('treats a parameter type the base method also declares as compatible', () => {
|
|
129
|
+
// The positive half of the base comparison: this is the branch that returns `true`,
|
|
130
|
+
// i.e. "I have a base contract AND it accepts this type". Without it the only
|
|
131
|
+
// observable outcome of the base path would be a violation, and a rule that reported
|
|
132
|
+
// every override with a resolvable base would still pass every other case here.
|
|
133
|
+
const base = `class BaseHandler { public void handle(DataSource ds) { } }`;
|
|
134
|
+
const violations = lspRule.check('Handlers.java', adapterFor([
|
|
135
|
+
{ name: 'BaseHandler', line: 1, code: base },
|
|
136
|
+
{
|
|
137
|
+
name: 'CsvHandler',
|
|
138
|
+
line: 2,
|
|
139
|
+
code: `class CsvHandler extends BaseHandler {
|
|
140
|
+
public void handle(DataSource ds) { }
|
|
141
|
+
}`,
|
|
142
|
+
},
|
|
143
|
+
]) as never);
|
|
144
|
+
|
|
145
|
+
expect(violations).toHaveLength(0);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('handles a generic and an array parameter type without crashing', () => {
|
|
149
|
+
// Exercises `normaliseTypeName`'s stripping of `<...>`, `[]` and `?`. Those suffixes
|
|
150
|
+
// are the reason `Map<String, Item>` does not get compared as the literal string
|
|
151
|
+
// "Map<String, Item>", and the array/optional forms are ordinary in both languages.
|
|
152
|
+
const base = `class BaseHandler { public void handle(Map<String, Item> index) { } }`;
|
|
153
|
+
const violations = lspRule.check('Handlers.java', adapterFor([
|
|
154
|
+
{ name: 'BaseHandler', line: 1, code: base },
|
|
155
|
+
{
|
|
156
|
+
name: 'CsvHandler',
|
|
157
|
+
line: 2,
|
|
158
|
+
code: `class CsvHandler extends BaseHandler {
|
|
159
|
+
public void handle(Map<String, Item> index) { }
|
|
160
|
+
}`,
|
|
161
|
+
},
|
|
162
|
+
]) as never);
|
|
163
|
+
|
|
164
|
+
expect(violations).toHaveLength(0);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('reads TypeScript `name: Type` parameters, not Java ones', () => {
|
|
168
|
+
// Every other case here runs the Java path. The rule is shared by both languages, and
|
|
169
|
+
// #588's whole lesson is that a parser which only works for one of them silently
|
|
170
|
+
// mangles the other — so the TypeScript path needs its own case, exercising
|
|
171
|
+
// `extractTsParamType` rather than `extractJavaParamType`.
|
|
172
|
+
const tsAdapter = { ...adapterFor([]), detectLanguage: () => 'typescript' };
|
|
173
|
+
const base = `class BaseHandler {
|
|
174
|
+
handle(ds: DataSource): void {}
|
|
175
|
+
}`;
|
|
176
|
+
const derived = `class CsvHandler extends BaseHandler {
|
|
177
|
+
handle(channel: FileChannel): void {}
|
|
178
|
+
}`;
|
|
179
|
+
const violations = lspRule.check('handlers.ts', {
|
|
180
|
+
...tsAdapter,
|
|
181
|
+
extractClasses: () => [
|
|
182
|
+
{ name: 'BaseHandler', line: 1, code: base },
|
|
183
|
+
{ name: 'CsvHandler', line: TS_DERIVED_CLASS_LINE, code: derived },
|
|
184
|
+
],
|
|
185
|
+
} as never);
|
|
186
|
+
|
|
187
|
+
expect(violations.length).toBeGreaterThan(0);
|
|
188
|
+
expect(violations[0].message).toContain('FileChannel');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('stays silent for a TypeScript override that matches the base', () => {
|
|
192
|
+
const tsAdapter = { ...adapterFor([]), detectLanguage: () => 'typescript' };
|
|
193
|
+
const base = `class BaseHandler {
|
|
194
|
+
handle(ds: DataSource): void {}
|
|
195
|
+
}`;
|
|
196
|
+
const derived = `class CsvHandler extends BaseHandler {
|
|
197
|
+
handle(ds: DataSource): void {}
|
|
198
|
+
}`;
|
|
199
|
+
const violations = lspRule.check('handlers.ts', {
|
|
200
|
+
...tsAdapter,
|
|
201
|
+
extractClasses: () => [
|
|
202
|
+
{ name: 'BaseHandler', line: 1, code: base },
|
|
203
|
+
{ name: 'CsvHandler', line: TS_DERIVED_CLASS_LINE, code: derived },
|
|
204
|
+
],
|
|
205
|
+
} as never);
|
|
206
|
+
|
|
207
|
+
expect(violations).toHaveLength(0);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('treats an adapter without detectLanguage as non-Java rather than crashing', () => {
|
|
211
|
+
// Older or third-party adapters may not implement `detectLanguage`. The rule must
|
|
212
|
+
// fall back to the `name: Type` reading instead of throwing on an absent method —
|
|
213
|
+
// a crash here would surface as a Gate 4 rule error, not as silence.
|
|
214
|
+
const adapterWithoutLanguage = {
|
|
215
|
+
parseAST: () => undefined,
|
|
216
|
+
extractFunctions: () => [],
|
|
217
|
+
countLines: () => 0,
|
|
218
|
+
extractClasses: () => [
|
|
219
|
+
{ name: 'BaseHandler', line: 1, code: `class BaseHandler {\n handle(ds: DataSource): void {}\n}` },
|
|
220
|
+
{
|
|
221
|
+
name: 'CsvHandler',
|
|
222
|
+
line: TS_DERIVED_CLASS_LINE,
|
|
223
|
+
code: `class CsvHandler extends BaseHandler {\n handle(channel: FileChannel): void {}\n}`,
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const violations = lspRule.check('handlers.ts', adapterWithoutLanguage as never);
|
|
229
|
+
|
|
230
|
+
expect(violations.length).toBeGreaterThan(0);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('ignores a TypeScript parameter with no type annotation', () => {
|
|
234
|
+
// `handle(id)` has no `: Type`, so `extractTsParamType` returns null and the parameter
|
|
235
|
+
// contributes nothing. Without this the missing-annotation path is untested after the
|
|
236
|
+
// #588 rewrite, and a regex change could start inventing types out of parameter names.
|
|
237
|
+
const tsAdapter = {
|
|
238
|
+
...adapterFor([]),
|
|
239
|
+
detectLanguage: () => 'typescript',
|
|
240
|
+
extractClasses: () => [
|
|
241
|
+
{ name: 'BaseHandler', line: 1, code: `class BaseHandler {\n handle(id): void {}\n}` },
|
|
242
|
+
{
|
|
243
|
+
name: 'CsvHandler',
|
|
244
|
+
line: TS_DERIVED_CLASS_LINE,
|
|
245
|
+
code: `class CsvHandler extends BaseHandler {\n handle(id): void {}\n}`,
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
expect(lspRule.check('handlers.ts', tsAdapter as never)).toHaveLength(0);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { lspRule } from '../../solid/lsp';
|
|
2
|
+
import { lspRule, buildViolation } from '../../solid/lsp';
|
|
3
3
|
|
|
4
4
|
const mockAdapter = {
|
|
5
5
|
detectLanguage: () => 'typescript',
|
|
@@ -10,7 +10,12 @@ const mockAdapter = {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
describe('lsp.ts - Liskov Substitution Principle Rule', () => {
|
|
13
|
-
it('
|
|
13
|
+
it('stays silent on a parameter type it cannot resolve against the base contract', () => {
|
|
14
|
+
// #588 changed what this rule is allowed to claim. `UserId` is neither a primitive nor
|
|
15
|
+
// a prefix of the class name, so a single-file analysis has no way to know what the
|
|
16
|
+
// BASE class declares — and "cannot determine" must not be reported as a violation.
|
|
17
|
+
// The previous expectation here asserted the defect: it read an unknown type as proof
|
|
18
|
+
// of incompatibility, which on Java flagged every class that has `extends`.
|
|
14
19
|
const mockAdapterWithViolation = {
|
|
15
20
|
...mockAdapter,
|
|
16
21
|
extractClasses: () => [{
|
|
@@ -19,11 +24,10 @@ describe('lsp.ts - Liskov Substitution Principle Rule', () => {
|
|
|
19
24
|
code: `class DerivedRepo extends BaseRepo { findById(id: UserId): Entity {} }`
|
|
20
25
|
}]
|
|
21
26
|
};
|
|
22
|
-
|
|
27
|
+
|
|
23
28
|
const violations = lspRule.check('test.ts', mockAdapterWithViolation as never);
|
|
24
|
-
|
|
25
|
-
expect(violations
|
|
26
|
-
expect(violations[0].ruleId).toBe('solid.lsp');
|
|
29
|
+
|
|
30
|
+
expect(violations).toHaveLength(0);
|
|
27
31
|
});
|
|
28
32
|
|
|
29
33
|
it('should pass for proper override', () => {
|
|
@@ -152,8 +156,10 @@ class DerivedRepo extends BaseRepo {
|
|
|
152
156
|
expect(violations).toHaveLength(0);
|
|
153
157
|
});
|
|
154
158
|
|
|
155
|
-
it('
|
|
156
|
-
//
|
|
159
|
+
it('stays silent even when the class name is missing (no name is not evidence)', () => {
|
|
160
|
+
// This used to report, on the reasoning that `className && ...` short-circuits to
|
|
161
|
+
// false. Under #588 that is precisely the defect: an ABSENT name is one more thing the
|
|
162
|
+
// analysis does not know, so it cannot justify a verdict.
|
|
157
163
|
const adapter = {
|
|
158
164
|
...mockAdapter,
|
|
159
165
|
extractClasses: () => [{
|
|
@@ -162,21 +168,19 @@ class DerivedRepo extends BaseRepo {
|
|
|
162
168
|
}],
|
|
163
169
|
};
|
|
164
170
|
const violations = lspRule.check('test.ts', adapter as never);
|
|
165
|
-
expect(violations
|
|
166
|
-
expect(violations[0].ruleId).toBe('solid.lsp');
|
|
171
|
+
expect(violations).toHaveLength(0);
|
|
167
172
|
});
|
|
168
173
|
|
|
169
|
-
it('falls back to line 0 when
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
expect(
|
|
180
|
-
expect(violations[0].line).toBe(0);
|
|
174
|
+
it('falls back to line 0 when a violation is built for a class with no line', () => {
|
|
175
|
+
// Asserted on the builder directly. After #588 `lspRule.check` can no longer reach
|
|
176
|
+
// this code path for an unknown type — that is the fix — so a case going through
|
|
177
|
+
// `check` would pass without exercising the `?? 0` fallback at all. A control that
|
|
178
|
+
// cannot fail proves nothing, which is why the import is explicit.
|
|
179
|
+
const violation = buildViolation('test.ts', { name: 'D' }, 'CustomId');
|
|
180
|
+
expect(violation.line).toBe(0);
|
|
181
|
+
expect(violation.ruleId).toBe('solid.lsp');
|
|
182
|
+
// And the line is honoured when present, so the fallback is not always taken.
|
|
183
|
+
const withLine = buildViolation('test.ts', { name: 'D', line: 42 }, 'CustomId');
|
|
184
|
+
expect(withLine.line).toBe(42);
|
|
181
185
|
});
|
|
182
186
|
});
|