@flint.fyi/rule-tester 0.20.0 → 0.21.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/dist/index.d.mts CHANGED
@@ -34,6 +34,10 @@ interface TestSuggestionForFile {
34
34
  updated: string;
35
35
  }
36
36
  interface TestSuggestionForFiles {
37
+ /**
38
+ * Expected edits keyed by paths relative to the test host's cwd, or absolute paths.
39
+ * Rules must report the corresponding absolute target paths.
40
+ */
37
41
  files: Record<string, TestSuggestionFileCase[]>;
38
42
  id: string;
39
43
  }
package/dist/index.mjs CHANGED
@@ -2,7 +2,8 @@ import assert from "node:assert/strict";
2
2
  import { isDeepStrictEqual } from "node:util";
3
3
  import { CachedFactory } from "cached-factory";
4
4
  import { resolve } from "pathe";
5
- import { applyChangesToText, createDiskBackedLinterHost, createEphemeralLinterHost, createVFSLinterHost, formatReport, getPositionOfColumnAndLine, isSuggestionForFiles, parseOptions, processRuleReport, withRepositoryRoot } from "@flint.fyi/core";
5
+ import { applyChangesToText, createEphemeralLinterHost, createVFSLinterHost, formatReport, getPositionOfColumnAndLine, isSuggestionForFiles, parseOptions, processRuleReport, withRepositoryRoot } from "@flint.fyi/core";
6
+ import { createDiskBackedLinterHost, isFileSystemCaseSensitive } from "@flint.fyi/core/node";
6
7
  import { isTruthy, normalizePath, nullThrows, pathKey } from "@flint.fyi/utils";
7
8
  //#region src/createOutput.ts
8
9
  function createOutput(reports, testCaseNormalized) {
@@ -96,13 +97,13 @@ function isTestSuggestionForFiles(suggestion) {
96
97
  }
97
98
  //#endregion
98
99
  //#region src/resolveReportedSuggestions.ts
99
- function resolveReportedSuggestions(reports, testCaseNormalized) {
100
+ function resolveReportedSuggestions(reports, testCaseNormalized, cwd) {
100
101
  const suggestionsReported = reports.flatMap((report) => report.suggestions).filter(isTruthy);
101
102
  if (!suggestionsReported.length) return;
102
103
  return suggestionsReported.map((suggestionReported, index) => {
103
104
  const suggestionExpected = testCaseNormalized.suggestions?.[index];
104
105
  if (isSuggestionForFiles(suggestionReported)) return {
105
- files: resolveReportedSuggestionForFiles(suggestionReported, suggestionExpected),
106
+ files: resolveReportedSuggestionForFiles(suggestionReported, suggestionExpected, cwd),
106
107
  id: suggestionReported.id
107
108
  };
108
109
  if (suggestionExpected && isTestSuggestionForFiles(suggestionExpected)) throw new Error("This test case describes a suggestion across files, but the rule is only reporting changes to its own file.");
@@ -112,12 +113,12 @@ function resolveReportedSuggestions(reports, testCaseNormalized) {
112
113
  };
113
114
  });
114
115
  }
115
- function resolveReportedSuggestionForFiles(suggestionReported, suggestionExpected) {
116
+ function resolveReportedSuggestionForFiles(suggestionReported, suggestionExpected, cwd) {
116
117
  if (!suggestionExpected) return {};
117
118
  if (!isTestSuggestionForFiles(suggestionExpected)) throw new Error("This test case describes a suggestion to its own file, but the rule is reporting changes across files.");
118
- assert.deepStrictEqual(Object.keys(suggestionReported.files).sort(), Object.keys(suggestionExpected.files).sort(), "Reported suggestion target paths must exactly match expected target paths.");
119
+ assert.deepStrictEqual(Object.keys(suggestionReported.files).toSorted(), Object.keys(suggestionExpected.files).map((filePath) => resolve(cwd, filePath)).toSorted(), "Reported suggestion target paths must exactly match expected target paths.");
119
120
  return Object.fromEntries(Object.entries(suggestionExpected.files).map(([filePath, suggestionCasesExpected]) => {
120
- const changes = suggestionReported.files[filePath];
121
+ const changes = suggestionReported.files[resolve(cwd, filePath)];
121
122
  assert.ok(changes, `Expected reported suggestion "${suggestionReported.id}" to provide changes for target "${filePath}".`);
122
123
  return [filePath, suggestionCasesExpected.map((suggestionCaseExpected) => ({
123
124
  original: suggestionCaseExpected.original,
@@ -163,7 +164,7 @@ async function runTestCaseRule(fileFactories, linterHost, { options, rule }, { c
163
164
  await ruleRuntime.teardown?.();
164
165
  }
165
166
  return {
166
- languageReports: collectLanguageReports ? rule.language.getLanguageReports?.(file) ?? [] : [],
167
+ languageReports: collectLanguageReports ? rule.language.getLanguageReports?.(file, linterHost) ?? [] : [],
167
168
  reports: reports.toSorted((a, b) => a.range.begin.raw - b.range.begin.raw || a.range.end.raw - b.range.end.raw)
168
169
  };
169
170
  }
@@ -174,18 +175,24 @@ var RuleTester = class {
174
175
  #linterHost;
175
176
  #testerOptions;
176
177
  constructor({ assertNoLanguageReports = true, defaults = {}, describe, diskBackedFSRoot, it, only, scope = globalThis, skip } = {}) {
177
- const virtualRoot = diskBackedFSRoot == null ? void 0 : resolve(process.cwd(), diskBackedFSRoot, "_flint-rule-tester-virtual");
178
- let baseHost = virtualRoot != null ? createEphemeralLinterHost(withRepositoryRoot(createDiskBackedLinterHost(virtualRoot), virtualRoot)) : void 0;
178
+ let host;
179
+ if (diskBackedFSRoot === void 0) host = createVFSLinterHost({
180
+ caseSensitive: isFileSystemCaseSensitive(),
181
+ cwd: "/"
182
+ });
183
+ else {
184
+ const cwd = resolve(process.cwd(), diskBackedFSRoot, "_flint-rule-tester-virtual");
185
+ host = createVFSLinterHost({ baseHost: createEphemeralLinterHost(withRepositoryRoot(createDiskBackedLinterHost(cwd), cwd)) });
186
+ }
179
187
  const { files: defaultFiles = {} } = defaults;
180
188
  if (Object.keys(defaultFiles).length) {
181
- const vfs = createVFSLinterHost(baseHost == null ? { cwd: process.cwd() } : { baseHost });
182
189
  for (const [name, content] of Object.entries(defaultFiles)) {
183
- const filePath = resolve(vfs.getCurrentDirectory(), name);
184
- vfs.vfsUpsertFile(filePath, content);
190
+ const filePath = resolve(host.getCurrentDirectory(), name);
191
+ host.vfsUpsertFile(filePath, content);
185
192
  }
186
- baseHost = vfs;
193
+ host = createVFSLinterHost({ baseHost: host });
187
194
  }
188
- this.#linterHost = createVFSLinterHost(baseHost == null ? { cwd: process.cwd() } : { baseHost });
195
+ this.#linterHost = host;
189
196
  this.#fileFactories = new CachedFactory((language) => language.createFileFactory(this.#linterHost));
190
197
  it = defaultTo(it, scope, "it");
191
198
  if (!skip && "skip" in it && typeof it.skip === "function") skip = it.skip;
@@ -226,16 +233,13 @@ var RuleTester = class {
226
233
  assert.equal(actualSnapshot, testCase.snapshot);
227
234
  const actualOutput = createOutput(reports, testCaseNormalized);
228
235
  assert.equal(testCase.output, actualOutput, "Expected `output` property to equal:");
229
- const actualSuggestions = resolveReportedSuggestions(reports, testCaseNormalized);
236
+ const actualSuggestions = resolveReportedSuggestions(reports, testCaseNormalized, this.#linterHost.getCurrentDirectory());
230
237
  assert.deepStrictEqual(actualSuggestions, testCase.suggestions);
231
238
  });
232
239
  }
233
240
  #itTestCase(testCase, seenTestCases, setup) {
234
241
  let test = testCase.only ? this.#testerOptions.only : this.#testerOptions.it;
235
- if (testCase.skip) {
236
- if ("skip" in test && typeof test.skip === "function") test = test.skip;
237
- else test = this.#testerOptions.skip;
238
- }
242
+ if (testCase.skip) test = "skip" in test && typeof test.skip === "function" ? test.skip : this.#testerOptions.skip;
239
243
  test(testCase.name ?? ("files" in testCase ? JSON.stringify({
240
244
  [testCase.fileName]: testCase.code,
241
245
  ...testCase.files
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flint.fyi/rule-tester",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "[Experimental] Rule unit tests for Flint.",
5
5
  "homepage": "https://flint.fyi",
6
6
  "repository": {
@@ -16,21 +16,22 @@
16
16
  "sideEffects": false,
17
17
  "type": "module",
18
18
  "exports": {
19
- ".": "./dist/index.mjs"
19
+ ".": "./dist/index.mjs",
20
+ "./package.json": "./package.json"
20
21
  },
21
22
  "files": [
22
23
  "dist/"
23
24
  ],
24
25
  "dependencies": {
25
- "@flint.fyi/core": "^0.27.0",
26
- "@flint.fyi/utils": "^0.16.0",
26
+ "@flint.fyi/core": "^0.29.0",
27
+ "@flint.fyi/utils": "^0.17.0",
27
28
  "cached-factory": "^0.3.0",
28
29
  "pathe": "^2.0.3"
29
30
  },
30
31
  "devDependencies": {
31
32
  "@flint.fyi/build": "^0.1.0",
32
33
  "tsdown": "0.23.0",
33
- "vitest": "5.0.1"
34
+ "vitest": "5.0.3"
34
35
  },
35
36
  "engines": {
36
37
  "node": ">=26.1.0"