@flint.fyi/rule-tester 0.14.3 → 0.16.1
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/lib/RuleTester.d.ts +38 -16
- package/lib/RuleTester.js +93 -92
- package/lib/createOutput.d.ts +5 -0
- package/lib/createOutput.js +10 -0
- package/lib/createReportSnapshot.d.ts +2 -1
- package/lib/createReportSnapshot.js +75 -35
- package/lib/index.d.ts +3 -2
- package/lib/index.js +2 -1
- package/lib/normalizeTestCase.d.ts +3 -2
- package/lib/normalizeTestCase.js +9 -5
- package/lib/predicates.d.ts +2 -1
- package/lib/predicates.js +7 -2
- package/lib/resolveReportedSuggestions.d.ts +6 -5
- package/lib/resolveReportedSuggestions.js +30 -42
- package/lib/runTestCaseRule.d.ts +6 -5
- package/lib/runTestCaseRule.js +39 -8
- package/lib/types.d.ts +38 -32
- package/package.json +22 -8
- package/CHANGELOG.md +0 -53
- package/lib/resolveReportedSuggestions.test.d.ts +0 -1
- package/lib/resolveReportedSuggestions.test.js +0 -172
- package/lib/types.js +0 -1
- package/src/RuleTester.ts +0 -186
- package/src/createReportSnapshot.ts +0 -50
- package/src/index.ts +0 -2
- package/src/normalizeTestCase.ts +0 -14
- package/src/predicates.ts +0 -7
- package/src/resolveReportedSuggestions.test.ts +0 -201
- package/src/resolveReportedSuggestions.ts +0 -85
- package/src/runTestCaseRule.ts +0 -35
- package/src/types.ts +0 -54
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
package/lib/RuleTester.d.ts
CHANGED
|
@@ -1,20 +1,42 @@
|
|
|
1
|
-
import { AnyOptionalSchema, AnyRule, InferredObject, RuleAbout } from "@flint.fyi/core";
|
|
2
1
|
import { InvalidTestCase, ValidTestCase } from "./types.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
import { AnyOptionalSchema, AnyRule, InferredInputObject, RuleAbout } from "@flint.fyi/core";
|
|
3
|
+
|
|
4
|
+
//#region src/RuleTester.d.ts
|
|
5
|
+
interface RuleTesterDefaults {
|
|
6
|
+
fileName?: string;
|
|
7
|
+
files?: Record<string, string>;
|
|
9
8
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
interface RuleTesterOptions {
|
|
10
|
+
defaults?: RuleTesterDefaults;
|
|
11
|
+
describe?: TesterSetupDescribe;
|
|
12
|
+
diskBackedFSRoot?: string;
|
|
13
|
+
it?: TesterSetupIt;
|
|
14
|
+
only?: TesterSetupIt;
|
|
15
|
+
scope?: Record<string, unknown>;
|
|
16
|
+
skip?: TesterSetupIt;
|
|
13
17
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
#private;
|
|
18
|
-
constructor({ describe, it, only, scope, skip, }?: RuleTesterOptions);
|
|
19
|
-
describe<OptionsSchema extends AnyOptionalSchema | undefined>(rule: AnyRule<RuleAbout, OptionsSchema>, { invalid, valid }: TestCases<InferredObject<OptionsSchema>>): void;
|
|
18
|
+
interface TestCases<Options extends object | undefined> {
|
|
19
|
+
invalid: InvalidTestCase<Options>[];
|
|
20
|
+
valid: ValidTestCase<Options>[];
|
|
20
21
|
}
|
|
22
|
+
type TesterSetupDescribe = (description: string, setup: () => void) => void;
|
|
23
|
+
type TesterSetupIt = (description: string, setup: () => Promise<void>) => void;
|
|
24
|
+
declare class RuleTester {
|
|
25
|
+
#private;
|
|
26
|
+
constructor({
|
|
27
|
+
defaults,
|
|
28
|
+
describe,
|
|
29
|
+
diskBackedFSRoot,
|
|
30
|
+
it,
|
|
31
|
+
only,
|
|
32
|
+
scope,
|
|
33
|
+
skip
|
|
34
|
+
}?: RuleTesterOptions);
|
|
35
|
+
describe<OptionsSchema extends AnyOptionalSchema | undefined>(rule: AnyRule<RuleAbout, OptionsSchema>, {
|
|
36
|
+
invalid,
|
|
37
|
+
valid
|
|
38
|
+
}: TestCases<InferredInputObject<OptionsSchema>>): void;
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { RuleTester };
|
|
42
|
+
//# sourceMappingURL=RuleTester.d.ts.map
|
package/lib/RuleTester.js
CHANGED
|
@@ -1,98 +1,99 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import assert from "node:assert";
|
|
1
|
+
import { createOutput } from "./createOutput.js";
|
|
3
2
|
import { createReportSnapshot } from "./createReportSnapshot.js";
|
|
4
3
|
import { normalizeTestCase } from "./normalizeTestCase.js";
|
|
5
4
|
import { resolveReportedSuggestions } from "./resolveReportedSuggestions.js";
|
|
6
5
|
import { runTestCaseRule } from "./runTestCaseRule.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
6
|
+
import { createDiskBackedLinterHost, createEphemeralLinterHost, createVFSLinterHost, parseOptions } from "@flint.fyi/core";
|
|
7
|
+
import { CachedFactory } from "cached-factory";
|
|
8
|
+
import assert from "node:assert/strict";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
//#region src/RuleTester.ts
|
|
11
|
+
var RuleTester = class {
|
|
12
|
+
#fileFactories;
|
|
13
|
+
#linterHost;
|
|
14
|
+
#testerOptions;
|
|
15
|
+
constructor({ defaults = {}, describe, diskBackedFSRoot, it, only, scope = globalThis, skip } = {}) {
|
|
16
|
+
let baseHost = diskBackedFSRoot != null ? createEphemeralLinterHost(createDiskBackedLinterHost(path.resolve(process.cwd(), diskBackedFSRoot, "_flint-rule-tester-virtual"))) : void 0;
|
|
17
|
+
const { files: defaultFiles = {} } = defaults;
|
|
18
|
+
if (Object.keys(defaultFiles).length) {
|
|
19
|
+
const vfs = createVFSLinterHost(baseHost == null ? { cwd: process.cwd() } : { baseHost });
|
|
20
|
+
for (const [name, content] of Object.entries(defaultFiles)) {
|
|
21
|
+
const filePath = path.resolve(vfs.getCurrentDirectory(), name);
|
|
22
|
+
vfs.vfsUpsertFile(filePath, content);
|
|
23
|
+
}
|
|
24
|
+
baseHost = vfs;
|
|
25
|
+
}
|
|
26
|
+
this.#linterHost = createVFSLinterHost(baseHost == null ? { cwd: process.cwd() } : { baseHost });
|
|
27
|
+
this.#fileFactories = new CachedFactory((language) => language.createFileFactory(this.#linterHost));
|
|
28
|
+
it = defaultTo(it, scope, "it");
|
|
29
|
+
if (!skip && "skip" in it && typeof it.skip === "function") skip = it.skip;
|
|
30
|
+
if (!only && "only" in it && typeof it.only === "function") only = it.only;
|
|
31
|
+
if (!skip) throw new TypeError("RuleTester needs a `skip` function");
|
|
32
|
+
if (!only) throw new TypeError("RuleTester needs a `only` function");
|
|
33
|
+
this.#testerOptions = {
|
|
34
|
+
defaults,
|
|
35
|
+
describe: defaultTo(describe, scope, "describe"),
|
|
36
|
+
it,
|
|
37
|
+
only,
|
|
38
|
+
scope,
|
|
39
|
+
skip
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
describe(rule, { invalid, valid }) {
|
|
43
|
+
this.#testerOptions.describe(rule.about.id, () => {
|
|
44
|
+
this.#testerOptions.describe("invalid", () => {
|
|
45
|
+
for (const testCase of invalid) this.#itInvalidCase(rule, testCase);
|
|
46
|
+
});
|
|
47
|
+
this.#testerOptions.describe("valid", () => {
|
|
48
|
+
for (const testCase of valid) this.#itValidCase(rule, testCase);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
#itInvalidCase(rule, testCase) {
|
|
53
|
+
const testCaseNormalized = normalizeTestCase(testCase, this.#testerOptions.defaults.fileName);
|
|
54
|
+
this.#itTestCase(testCaseNormalized, async () => {
|
|
55
|
+
const reports = await runTestCaseRule(this.#fileFactories, this.#linterHost, {
|
|
56
|
+
options: parseOptions(rule.options, testCase.options),
|
|
57
|
+
rule
|
|
58
|
+
}, testCaseNormalized);
|
|
59
|
+
const actualSnapshot = createReportSnapshot(testCase.code, reports);
|
|
60
|
+
assert.equal(actualSnapshot, testCase.snapshot);
|
|
61
|
+
const actualOutput = createOutput(reports, testCaseNormalized);
|
|
62
|
+
assert.equal(testCase.output, actualOutput, "Expected `output` property to equal:");
|
|
63
|
+
const actualSuggestions = resolveReportedSuggestions(reports, testCaseNormalized);
|
|
64
|
+
assert.deepStrictEqual(actualSuggestions, testCase.suggestions);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
#itTestCase(testCase, setup) {
|
|
68
|
+
let test = testCase.only ? this.#testerOptions.only : this.#testerOptions.it;
|
|
69
|
+
if (testCase.skip) if ("skip" in test && typeof test.skip === "function") test = test.skip;
|
|
70
|
+
else test = this.#testerOptions.skip;
|
|
71
|
+
test(testCase.name ?? ("files" in testCase ? JSON.stringify({
|
|
72
|
+
[testCase.fileName]: testCase.code,
|
|
73
|
+
...testCase.files
|
|
74
|
+
}, null, 2) : testCase.code), () => {
|
|
75
|
+
if (testCase.files != null) assert.notEqual(Object.keys(testCase.files), 0, `'files' must have at least one file`);
|
|
76
|
+
return setup();
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
#itValidCase(rule, testCaseRaw) {
|
|
80
|
+
const testCase = typeof testCaseRaw === "string" ? { code: testCaseRaw } : testCaseRaw;
|
|
81
|
+
const testCaseNormalized = normalizeTestCase(testCase, this.#testerOptions.defaults.fileName);
|
|
82
|
+
this.#itTestCase(testCaseNormalized, async () => {
|
|
83
|
+
const reports = await runTestCaseRule(this.#fileFactories, this.#linterHost, {
|
|
84
|
+
options: parseOptions(rule.options, testCase.options),
|
|
85
|
+
rule
|
|
86
|
+
}, testCaseNormalized);
|
|
87
|
+
if (reports.length) assert.deepStrictEqual(createReportSnapshot(testCaseNormalized.code, reports), testCaseNormalized.code);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
};
|
|
90
91
|
function defaultTo(provided, scope, scopeKey) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (scopeKey in scope && typeof scope[scopeKey] === "function") {
|
|
95
|
-
return scope[scopeKey];
|
|
96
|
-
}
|
|
97
|
-
throw new Error(`No ${scopeKey} function found`);
|
|
92
|
+
if (provided) return provided;
|
|
93
|
+
if (scopeKey in scope && typeof scope[scopeKey] === "function") return scope[scopeKey];
|
|
94
|
+
throw new Error(`No ${scopeKey} function found`);
|
|
98
95
|
}
|
|
96
|
+
//#endregion
|
|
97
|
+
export { RuleTester };
|
|
98
|
+
|
|
99
|
+
//# sourceMappingURL=RuleTester.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type NormalizedReport } from "@flint.fyi/core";
|
|
2
|
+
import type { TestCaseNormalized } from "./normalizeTestCase.ts";
|
|
3
|
+
import type { InvalidTestCase } from "./types.ts";
|
|
4
|
+
export declare function createOutput(reports: NormalizedReport[], testCaseNormalized: InvalidTestCase & TestCaseNormalized): string | undefined;
|
|
5
|
+
//# sourceMappingURL=createOutput.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { applyChangesToText } from "@flint.fyi/core";
|
|
2
|
+
//#region src/createOutput.ts
|
|
3
|
+
function createOutput(reports, testCaseNormalized) {
|
|
4
|
+
const changes = reports.filter((report) => report.fix !== void 0).flatMap((report) => report.fix);
|
|
5
|
+
return changes.length ? applyChangesToText(changes, testCaseNormalized.code) : void 0;
|
|
6
|
+
}
|
|
7
|
+
//#endregion
|
|
8
|
+
export { createOutput };
|
|
9
|
+
|
|
10
|
+
//# sourceMappingURL=createOutput.js.map
|
|
@@ -1,38 +1,78 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { formatReport, getPositionOfColumnAndLine } from "@flint.fyi/core";
|
|
2
|
+
import { nullThrows } from "@flint.fyi/utils";
|
|
3
|
+
//#region src/createReportSnapshot.ts
|
|
4
|
+
function createReportSnapshot(sourceText, reports) {
|
|
5
|
+
let result = sourceText;
|
|
6
|
+
for (const report of reports.toReversed()) result = createReportSnapshotAt(result, report);
|
|
7
|
+
return result;
|
|
8
8
|
}
|
|
9
9
|
function createReportSnapshotAt(sourceText, report) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
else {
|
|
31
|
-
const squiggleEnd = i === end.line ? end.column : line.length;
|
|
32
|
-
output.push(prevLineIndent.padEnd(squiggleEnd, "~"));
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return (sourceText.slice(0, lineStartIndex) +
|
|
36
|
-
output.join("\n") +
|
|
37
|
-
sourceText.slice(lineEndIndex));
|
|
10
|
+
const { begin, end } = getDisplayedRange(sourceText, report.range);
|
|
11
|
+
const lineStartIndex = begin.raw - begin.column;
|
|
12
|
+
const lineEndIndex = getLineBounds(sourceText, end.line).end;
|
|
13
|
+
const lines = sourceText.slice(lineStartIndex, lineEndIndex).split("\n");
|
|
14
|
+
const output = [];
|
|
15
|
+
for (let i = begin.line; i <= end.line; i++) {
|
|
16
|
+
const line = nullThrows(lines[i - begin.line], "Line is expected to be present by the loop condition");
|
|
17
|
+
output.push(line);
|
|
18
|
+
const prevLineIndent = /^[\t ]*/.exec(line)?.[0] ?? "";
|
|
19
|
+
if (i === begin.line) {
|
|
20
|
+
const indent = prevLineIndent.padEnd(begin.column, " ");
|
|
21
|
+
const squiggleEnd = begin.line === end.line ? end.column : line.length;
|
|
22
|
+
output.push(indent.padEnd(squiggleEnd, "~"));
|
|
23
|
+
for (const errorMessageLine of formatReport(report.data, report.message.primary).split("\n")) output.push(indent + errorMessageLine);
|
|
24
|
+
} else {
|
|
25
|
+
const squiggleEnd = i === end.line ? end.column : line.length;
|
|
26
|
+
output.push(prevLineIndent.padEnd(squiggleEnd, "~"));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return sourceText.slice(0, lineStartIndex) + output.join("\n") + sourceText.slice(lineEndIndex);
|
|
38
30
|
}
|
|
31
|
+
function getDisplayedRange(sourceText, { begin, end }) {
|
|
32
|
+
if (end.column > 0 || end.line === begin.line) return {
|
|
33
|
+
begin,
|
|
34
|
+
end
|
|
35
|
+
};
|
|
36
|
+
const previousLine = getLineBounds(sourceText, end.line - 1);
|
|
37
|
+
if (begin.line === previousLine.line && previousLine.text === "") {
|
|
38
|
+
const currentLine = getLineBounds(sourceText, end.line);
|
|
39
|
+
if (currentLine.text !== "") return {
|
|
40
|
+
begin: {
|
|
41
|
+
column: 0,
|
|
42
|
+
line: currentLine.line,
|
|
43
|
+
raw: currentLine.start
|
|
44
|
+
},
|
|
45
|
+
end: {
|
|
46
|
+
column: 1,
|
|
47
|
+
line: currentLine.line,
|
|
48
|
+
raw: currentLine.start + 1
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
begin,
|
|
54
|
+
end: {
|
|
55
|
+
column: previousLine.text.length,
|
|
56
|
+
line: previousLine.line,
|
|
57
|
+
raw: previousLine.end
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function getLineBounds(sourceText, line) {
|
|
62
|
+
const start = getPositionOfColumnAndLine(sourceText, {
|
|
63
|
+
column: 0,
|
|
64
|
+
line
|
|
65
|
+
});
|
|
66
|
+
let end = sourceText.indexOf("\n", start);
|
|
67
|
+
if (end < 0) end = sourceText.length;
|
|
68
|
+
return {
|
|
69
|
+
end,
|
|
70
|
+
line,
|
|
71
|
+
start,
|
|
72
|
+
text: sourceText.slice(start, end)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
export { createReportSnapshot };
|
|
77
|
+
|
|
78
|
+
//# sourceMappingURL=createReportSnapshot.js.map
|
package/lib/index.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { InvalidTestCase, TestCase, TestSuggestion, TestSuggestionFileCase, TestSuggestionForFile, TestSuggestionForFiles, ValidTestCase, ValidTestCaseObject } from "./types.js";
|
|
2
|
+
import { RuleTester } from "./RuleTester.js";
|
|
3
|
+
export { InvalidTestCase, RuleTester, TestCase, TestSuggestion, TestSuggestionFileCase, TestSuggestionForFile, TestSuggestionForFiles, ValidTestCase, ValidTestCaseObject };
|
package/lib/index.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { RuleTester } from "./RuleTester.js";
|
|
2
|
+
export { RuleTester };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { TestCase } from "./types.
|
|
1
|
+
import type { TestCase } from "./types.ts";
|
|
2
2
|
export interface TestCaseNormalized extends TestCase {
|
|
3
3
|
fileName: string;
|
|
4
4
|
}
|
|
5
|
-
export declare function normalizeTestCase<T extends TestCase>(testCase: T): T & TestCaseNormalized;
|
|
5
|
+
export declare function normalizeTestCase<T extends TestCase>(testCase: T, fileName: string | undefined): T & TestCaseNormalized;
|
|
6
|
+
//# sourceMappingURL=normalizeTestCase.d.ts.map
|
package/lib/normalizeTestCase.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
//#region src/normalizeTestCase.ts
|
|
2
|
+
function normalizeTestCase(testCase, fileName) {
|
|
3
|
+
const rv = { ...testCase };
|
|
4
|
+
rv.fileName ??= fileName ?? "file.ts";
|
|
5
|
+
return rv;
|
|
6
6
|
}
|
|
7
|
+
//#endregion
|
|
8
|
+
export { normalizeTestCase };
|
|
9
|
+
|
|
10
|
+
//# sourceMappingURL=normalizeTestCase.js.map
|
package/lib/predicates.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import { TestSuggestion, TestSuggestionForFiles } from "./types.
|
|
1
|
+
import type { TestSuggestion, TestSuggestionForFiles } from "./types.ts";
|
|
2
2
|
export declare function isTestSuggestionForFiles(suggestion: TestSuggestion): suggestion is TestSuggestionForFiles;
|
|
3
|
+
//# sourceMappingURL=predicates.d.ts.map
|
package/lib/predicates.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
//#region src/predicates.ts
|
|
2
|
+
function isTestSuggestionForFiles(suggestion) {
|
|
3
|
+
return "files" in suggestion;
|
|
3
4
|
}
|
|
5
|
+
//#endregion
|
|
6
|
+
export { isTestSuggestionForFiles };
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=predicates.js.map
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { NormalizedReport } from "@flint.fyi/core";
|
|
2
|
-
import { TestCaseNormalized } from "./normalizeTestCase.
|
|
3
|
-
import { InvalidTestCase, TestSuggestionFileCase } from "./types.
|
|
1
|
+
import { type NormalizedReport } from "@flint.fyi/core";
|
|
2
|
+
import type { TestCaseNormalized } from "./normalizeTestCase.ts";
|
|
3
|
+
import type { InvalidTestCase, TestSuggestionFileCase } from "./types.ts";
|
|
4
4
|
export declare function resolveReportedSuggestions(reports: NormalizedReport[], testCaseNormalized: InvalidTestCase & TestCaseNormalized): ({
|
|
5
5
|
files: {
|
|
6
6
|
[k: string]: TestSuggestionFileCase[];
|
|
7
7
|
};
|
|
8
8
|
id: string;
|
|
9
|
-
updated?:
|
|
9
|
+
updated?: never;
|
|
10
10
|
} | {
|
|
11
11
|
id: string;
|
|
12
12
|
updated: string;
|
|
13
|
-
files?:
|
|
13
|
+
files?: never;
|
|
14
14
|
})[] | undefined;
|
|
15
|
+
//# sourceMappingURL=resolveReportedSuggestions.d.ts.map
|
|
@@ -1,46 +1,34 @@
|
|
|
1
|
-
import { applyChangesToText, isSuggestionForFiles, } from "@flint.fyi/core";
|
|
2
|
-
import { isTruthy } from "@flint.fyi/utils";
|
|
3
1
|
import { isTestSuggestionForFiles } from "./predicates.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
id: suggestionReported.id,
|
|
18
|
-
updated: applyChangesToText([suggestionReported], testCaseNormalized.code),
|
|
19
|
-
});
|
|
2
|
+
import { applyChangesToText, isSuggestionForFiles } from "@flint.fyi/core";
|
|
3
|
+
import { isTruthy } from "@flint.fyi/utils";
|
|
4
|
+
//#region src/resolveReportedSuggestions.ts
|
|
5
|
+
function resolveReportedSuggestions(reports, testCaseNormalized) {
|
|
6
|
+
const suggestionsReported = reports.flatMap((report) => report.suggestions).filter(isTruthy);
|
|
7
|
+
if (!suggestionsReported.length) return;
|
|
8
|
+
return suggestionsReported.map((suggestionReported) => isSuggestionForFiles(suggestionReported) ? {
|
|
9
|
+
files: resolveReportedSuggestionForFiles(suggestionReported, testCaseNormalized),
|
|
10
|
+
id: suggestionReported.id
|
|
11
|
+
} : {
|
|
12
|
+
id: suggestionReported.id,
|
|
13
|
+
updated: applyChangesToText([suggestionReported], testCaseNormalized.code)
|
|
14
|
+
});
|
|
20
15
|
}
|
|
21
16
|
function resolveReportedSuggestionForFiles(suggestionReported, testCaseNormalized) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
original: suggestionCaseExpected.original,
|
|
37
|
-
updated: changes
|
|
38
|
-
? applyChangesToText(changes, suggestionCaseExpected.original)
|
|
39
|
-
: suggestionCaseExpected.original,
|
|
40
|
-
};
|
|
41
|
-
}),
|
|
42
|
-
];
|
|
43
|
-
});
|
|
44
|
-
})
|
|
45
|
-
.flat());
|
|
17
|
+
if (!testCaseNormalized.suggestions) return {};
|
|
18
|
+
if (!testCaseNormalized.suggestions.every(isTestSuggestionForFiles)) throw new Error("This test case describes suggestions across files, but the rule is only reporting changes to its own file.");
|
|
19
|
+
return Object.fromEntries(testCaseNormalized.suggestions.flatMap((suggestionExpected) => {
|
|
20
|
+
return Object.entries(suggestionExpected.files).map(([filePath, suggestionCasesExpected]) => {
|
|
21
|
+
return [filePath, suggestionCasesExpected.map((suggestionCaseExpected) => {
|
|
22
|
+
const changes = suggestionReported.files[filePath];
|
|
23
|
+
return {
|
|
24
|
+
original: suggestionCaseExpected.original,
|
|
25
|
+
updated: changes ? applyChangesToText(changes, suggestionCaseExpected.original) : suggestionCaseExpected.original
|
|
26
|
+
};
|
|
27
|
+
})];
|
|
28
|
+
});
|
|
29
|
+
}));
|
|
46
30
|
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { resolveReportedSuggestions };
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=resolveReportedSuggestions.js.map
|
package/lib/runTestCaseRule.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { AnyLanguage, AnyOptionalSchema, AnyRule,
|
|
2
|
-
import { CachedFactory } from "cached-factory";
|
|
3
|
-
import { TestCaseNormalized } from "./normalizeTestCase.
|
|
1
|
+
import { type AnyLanguage, type AnyLanguageFileFactory, type AnyOptionalSchema, type AnyRule, type InferredOutputObject, type NormalizedReport, type RuleAbout, type VFSLinterHost } from "@flint.fyi/core";
|
|
2
|
+
import type { CachedFactory } from "cached-factory";
|
|
3
|
+
import type { TestCaseNormalized } from "./normalizeTestCase.ts";
|
|
4
4
|
export interface TestCaseRuleConfiguration<OptionsSchema extends AnyOptionalSchema | undefined> {
|
|
5
|
-
options?:
|
|
5
|
+
options?: InferredOutputObject<OptionsSchema | undefined>;
|
|
6
6
|
rule: AnyRule<RuleAbout, OptionsSchema>;
|
|
7
7
|
}
|
|
8
|
-
export declare function runTestCaseRule<OptionsSchema extends AnyOptionalSchema | undefined>(fileFactories: CachedFactory<AnyLanguage,
|
|
8
|
+
export declare function runTestCaseRule<OptionsSchema extends AnyOptionalSchema | undefined>(fileFactories: CachedFactory<AnyLanguage, AnyLanguageFileFactory>, linterHost: VFSLinterHost, { options, rule }: Required<TestCaseRuleConfiguration<OptionsSchema>>, { code, fileName, files }: TestCaseNormalized): Promise<NormalizedReport[]>;
|
|
9
|
+
//# sourceMappingURL=runTestCaseRule.d.ts.map
|
package/lib/runTestCaseRule.js
CHANGED
|
@@ -1,9 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { processRuleReport } from "@flint.fyi/core";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { normalizePath, pathKey } from "@flint.fyi/utils";
|
|
5
|
+
//#region src/runTestCaseRule.ts
|
|
6
|
+
async function runTestCaseRule(fileFactories, linterHost, { options, rule }, { code, fileName, files }) {
|
|
7
|
+
const filePathAbsolute = normalizePath(path.resolve(linterHost.getCurrentDirectory(), fileName));
|
|
8
|
+
const caseSensitive = linterHost.isCaseSensitiveFS();
|
|
9
|
+
const targetKey = pathKey(filePathAbsolute, caseSensitive);
|
|
10
|
+
for (const oldFile of linterHost.vfsListFiles().keys()) if (pathKey(oldFile, caseSensitive) !== targetKey) linterHost.vfsDeleteFile(oldFile);
|
|
11
|
+
for (const [name, content] of Object.entries(files ?? {})) {
|
|
12
|
+
const filePath = normalizePath(path.resolve(linterHost.getCurrentDirectory(), name));
|
|
13
|
+
assert.notEqual(filePath, filePathAbsolute, `Expected 'files' not to shadow '${fileName}'`);
|
|
14
|
+
linterHost.vfsUpsertFile(filePath, content);
|
|
15
|
+
}
|
|
16
|
+
linterHost.vfsUpsertFile(filePathAbsolute, code);
|
|
17
|
+
using file = fileFactories.get(rule.language).createFile({
|
|
18
|
+
filePath: fileName,
|
|
19
|
+
filePathAbsolute,
|
|
20
|
+
sourceText: code
|
|
21
|
+
});
|
|
22
|
+
const reports = [];
|
|
23
|
+
const ruleRuntime = await rule.setup({
|
|
24
|
+
host: linterHost,
|
|
25
|
+
report(ruleReport) {
|
|
26
|
+
const processedReport = processRuleReport(file, rule, ruleReport);
|
|
27
|
+
if (processedReport == null) return;
|
|
28
|
+
reports.push(processedReport);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
if (ruleRuntime) {
|
|
32
|
+
rule.language.runFileVisitors(file, options, ruleRuntime);
|
|
33
|
+
await ruleRuntime.teardown?.();
|
|
34
|
+
}
|
|
35
|
+
return reports;
|
|
9
36
|
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { runTestCaseRule };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=runTestCaseRule.js.map
|