@sarj/eslint-plugin 16.36.7 → 16.38.52
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.cjs +907 -767
- package/dist/index.js +901 -761
- package/dist/rule-examples.cjs +168 -44
- package/dist/rule-examples.js +168 -44
- package/package.json +1 -1
package/dist/rule-examples.cjs
CHANGED
|
@@ -35,68 +35,192 @@ __export(verify_rule_examples_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(verify_rule_examples_exports);
|
|
36
36
|
var import_promises = require("fs/promises");
|
|
37
37
|
var import_node_os = require("os");
|
|
38
|
+
var import_node_path2 = require("path");
|
|
39
|
+
var parser2 = __toESM(require("@typescript-eslint/parser"), 1);
|
|
40
|
+
var import_eslint = require("eslint");
|
|
41
|
+
|
|
42
|
+
// src/create-example-parser.ts
|
|
38
43
|
var import_node_path = require("path");
|
|
39
44
|
var parser = __toESM(require("@typescript-eslint/parser"), 1);
|
|
40
|
-
var
|
|
45
|
+
var import_typescript = __toESM(require("typescript"), 1);
|
|
46
|
+
var WATCH_DEFAULTS = {
|
|
47
|
+
noEmit: true,
|
|
48
|
+
noUnusedLocals: true,
|
|
49
|
+
noUnusedParameters: true,
|
|
50
|
+
allowJs: true,
|
|
51
|
+
allowNonTsExtensions: true,
|
|
52
|
+
checkJs: true
|
|
53
|
+
};
|
|
54
|
+
var MAX_LIBRARY_FILES = 256;
|
|
55
|
+
var MAX_LIBRARY_BYTES = 16 * 1024 * 1024;
|
|
56
|
+
function createExampleParser() {
|
|
57
|
+
const libraryRoot = import_typescript.default.sys.realpath?.((0, import_node_path.dirname)(import_typescript.default.getDefaultLibFilePath({})));
|
|
58
|
+
const cache = /* @__PURE__ */ new Map();
|
|
59
|
+
const registries = /* @__PURE__ */ new Map();
|
|
60
|
+
const counts = { programs: 0, libraryAcquires: 0, libraryHits: 0, nativeFallbacks: 0 };
|
|
61
|
+
let bytes = 0;
|
|
62
|
+
return {
|
|
63
|
+
meta: parser.meta,
|
|
64
|
+
clear() {
|
|
65
|
+
cache.clear();
|
|
66
|
+
registries.clear();
|
|
67
|
+
bytes = 0;
|
|
68
|
+
},
|
|
69
|
+
statistics() {
|
|
70
|
+
return { ...counts, libraryFiles: cache.size, libraryBytes: bytes };
|
|
71
|
+
},
|
|
72
|
+
parseForESLint(code, options) {
|
|
73
|
+
const fallback = () => {
|
|
74
|
+
counts.nativeFallbacks += 1;
|
|
75
|
+
return parser.parseForESLint(code, options);
|
|
76
|
+
};
|
|
77
|
+
if (!Object.hasOwn(import_typescript.default, "JSDocParsingMode")) return fallback();
|
|
78
|
+
if (typeof options?.project !== "string" || typeof options.filePath !== "string" || options.programs != null || options.projectService || options.extraFileExtensions?.length || options.debugLevel) {
|
|
79
|
+
return fallback();
|
|
80
|
+
}
|
|
81
|
+
const config = (0, import_node_path.resolve)(options.tsconfigRootDir ?? process.cwd(), options.project);
|
|
82
|
+
let unrecoverable = false;
|
|
83
|
+
const parsed = import_typescript.default.getParsedCommandLineOfConfigFile(config, WATCH_DEFAULTS, {
|
|
84
|
+
...import_typescript.default.sys,
|
|
85
|
+
onUnRecoverableConfigFileDiagnostic() {
|
|
86
|
+
unrecoverable = true;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
if (unrecoverable || parsed === void 0 || parsed.errors.length > 0 || parsed.projectReferences?.length) {
|
|
90
|
+
return fallback();
|
|
91
|
+
}
|
|
92
|
+
const filename = (0, import_node_path.resolve)(options.filePath);
|
|
93
|
+
const host = import_typescript.default.createCompilerHost(parsed.options, true);
|
|
94
|
+
const canonical = (path) => host.getCanonicalFileName((0, import_node_path.resolve)(path));
|
|
95
|
+
if (!parsed.fileNames.some((path) => canonical(path) === canonical(filename))) return fallback();
|
|
96
|
+
const mode = options.jsDocParsingMode === "none" ? import_typescript.default.JSDocParsingMode.ParseNone : options.jsDocParsingMode === "type-info" ? import_typescript.default.JSDocParsingMode.ParseForTypeInfo : import_typescript.default.JSDocParsingMode.ParseAll;
|
|
97
|
+
host.jsDocParsingMode = mode;
|
|
98
|
+
let registry = registries.get(mode);
|
|
99
|
+
if (registry === void 0) {
|
|
100
|
+
registry = import_typescript.default.createDocumentRegistry(import_typescript.default.sys.useCaseSensitiveFileNames, process.cwd(), mode);
|
|
101
|
+
registries.set(mode, registry);
|
|
102
|
+
}
|
|
103
|
+
const settingsKey = registry.getKeyForCompilationSettings(parsed.options);
|
|
104
|
+
const read = host.readFile.bind(host);
|
|
105
|
+
host.readFile = (path) => canonical(path) === canonical(filename) ? code : read(path);
|
|
106
|
+
const original = host.getSourceFile.bind(host);
|
|
107
|
+
host.getSourceFile = (path, language, onError, fresh) => {
|
|
108
|
+
if (fresh || libraryRoot === void 0 || host.realpath === void 0 || canonical(path) === canonical(filename) || (0, import_node_path.dirname)((0, import_node_path.resolve)(path)) !== libraryRoot || !/^lib(?:\..+)?\.d\.ts$/u.test((0, import_node_path.basename)(path)) || !host.fileExists(path)) {
|
|
109
|
+
return original(path, language, onError, fresh);
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
if (host.realpath(path) !== (0, import_node_path.resolve)(path)) return original(path, language, onError, fresh);
|
|
113
|
+
} catch {
|
|
114
|
+
return original(path, language, onError, fresh);
|
|
115
|
+
}
|
|
116
|
+
const text = read(path);
|
|
117
|
+
if (text === void 0) return original(path, language, onError, fresh);
|
|
118
|
+
const version = typeof language === "number" ? language : language.languageVersion;
|
|
119
|
+
const format = typeof language === "number" ? void 0 : language.impliedNodeFormat;
|
|
120
|
+
const key = JSON.stringify([path, settingsKey, version, format, mode]);
|
|
121
|
+
const previous = cache.get(key);
|
|
122
|
+
if (previous?.text === text) {
|
|
123
|
+
counts.libraryHits += 1;
|
|
124
|
+
return previous.source;
|
|
125
|
+
}
|
|
126
|
+
const size = Buffer.byteLength(text);
|
|
127
|
+
if (previous !== void 0 || cache.size >= MAX_LIBRARY_FILES || bytes + size > MAX_LIBRARY_BYTES) {
|
|
128
|
+
return original(path, language, onError, fresh);
|
|
129
|
+
}
|
|
130
|
+
const source = registry.acquireDocument(
|
|
131
|
+
path,
|
|
132
|
+
parsed.options,
|
|
133
|
+
import_typescript.default.ScriptSnapshot.fromString(text),
|
|
134
|
+
"unchanged-native-library",
|
|
135
|
+
import_typescript.default.ScriptKind.TS,
|
|
136
|
+
language
|
|
137
|
+
);
|
|
138
|
+
cache.set(key, { text, source });
|
|
139
|
+
bytes += size;
|
|
140
|
+
counts.libraryAcquires += 1;
|
|
141
|
+
return source;
|
|
142
|
+
};
|
|
143
|
+
const program = import_typescript.default.createProgram(parsed.fileNames, parsed.options, host);
|
|
144
|
+
counts.programs += 1;
|
|
145
|
+
if (program.getSourceFile(filename)?.text !== code) {
|
|
146
|
+
throw new Error("The example compiler did not load the current lint source.");
|
|
147
|
+
}
|
|
148
|
+
return parser.parseForESLint(code, {
|
|
149
|
+
...options,
|
|
150
|
+
project: false,
|
|
151
|
+
projectService: false,
|
|
152
|
+
programs: [program],
|
|
153
|
+
disallowAutomaticSingleRunInference: true
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/verify-rule-examples.ts
|
|
41
160
|
async function verifyRuleExamples(rule, setup = {}) {
|
|
42
161
|
const spec = rule.documentation;
|
|
43
162
|
if (spec === void 0 || !spec.publicExamples.some((item) => item.outcome === "match") || !spec.publicExamples.some((item) => item.outcome === "no-match")) {
|
|
44
163
|
throw new Error("rule requires documented public matching and non-matching examples");
|
|
45
164
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
165
|
+
const exampleParser = createExampleParser();
|
|
166
|
+
try {
|
|
167
|
+
for (const example of spec.examples) {
|
|
168
|
+
const root = await (0, import_promises.mkdtemp)((0, import_node_path2.join)((0, import_node_os.tmpdir)(), "sarj-rule-example-"));
|
|
169
|
+
try {
|
|
170
|
+
for (const file of example.files) {
|
|
171
|
+
const path = fixturePath(root, file.path);
|
|
172
|
+
validateFixtureSyntax(example.id, file, path);
|
|
173
|
+
await (0, import_promises.mkdir)((0, import_node_path2.dirname)(path), { recursive: true });
|
|
174
|
+
await (0, import_promises.writeFile)(path, file.source);
|
|
175
|
+
}
|
|
176
|
+
for (const dependency of example.installedDependencies ?? []) {
|
|
177
|
+
if (setup.installedDependencyRoot === void 0) throw new Error(`${example.id}: explicit installed dependency root is required`);
|
|
178
|
+
const packageFile = fixturePath((0, import_node_path2.resolve)(setup.installedDependencyRoot), `node_modules/${dependency.module}/package.json`);
|
|
179
|
+
const metadata = JSON.parse(await (0, import_promises.readFile)(packageFile, "utf8"));
|
|
180
|
+
if (typeof metadata !== "object" || metadata === null || !("name" in metadata) || !("version" in metadata) || metadata.name !== dependency.module || metadata.version !== dependency.version) throw new Error(`${example.id}: installed dependency identity/version mismatch for ${dependency.module}`);
|
|
181
|
+
const destination = fixturePath(root, `node_modules/${dependency.module}`);
|
|
182
|
+
await (0, import_promises.mkdir)((0, import_node_path2.dirname)(destination), { recursive: true });
|
|
183
|
+
await (0, import_promises.symlink)((0, import_node_path2.dirname)(packageFile), destination, process.platform === "win32" ? "junction" : "dir");
|
|
184
|
+
}
|
|
185
|
+
const tsconfig = (0, import_node_path2.join)(root, "tsconfig.json");
|
|
186
|
+
if (!example.files.some((file) => file.path === "tsconfig.json")) {
|
|
187
|
+
await (0, import_promises.writeFile)(tsconfig, JSON.stringify({ compilerOptions: {
|
|
188
|
+
target: "ESNext",
|
|
189
|
+
module: "ESNext",
|
|
190
|
+
moduleResolution: "Bundler",
|
|
191
|
+
strict: true,
|
|
192
|
+
allowJs: true,
|
|
193
|
+
jsx: "preserve",
|
|
194
|
+
noEmit: true
|
|
195
|
+
}, include: ["**/*"] }));
|
|
196
|
+
}
|
|
197
|
+
const ruleId = `@sarj/${spec.ruleId}`;
|
|
198
|
+
const config = {
|
|
199
|
+
files: ["**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}"],
|
|
200
|
+
languageOptions: { parser: exampleParser, parserOptions: { project: tsconfig, tsconfigRootDir: root, disallowAutomaticSingleRunInference: true } },
|
|
201
|
+
plugins: { "@sarj": { rules: { [spec.ruleId]: rule } } },
|
|
202
|
+
rules: { [ruleId]: ["error", ...example.ruleOptions ?? rule.defaultOptions ?? []] }
|
|
203
|
+
};
|
|
204
|
+
await verifyExample(new import_eslint.Linter({ cwd: root }), config, example, root, ruleId);
|
|
205
|
+
} finally {
|
|
206
|
+
parser2.clearCaches();
|
|
207
|
+
await (0, import_promises.rm)(root, { recursive: true, force: true });
|
|
75
208
|
}
|
|
76
|
-
const ruleId = `@sarj/${spec.ruleId}`;
|
|
77
|
-
const config = {
|
|
78
|
-
files: ["**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}"],
|
|
79
|
-
languageOptions: { parser, parserOptions: { project: tsconfig, tsconfigRootDir: root, disallowAutomaticSingleRunInference: true } },
|
|
80
|
-
plugins: { "@sarj": { rules: { [spec.ruleId]: rule } } },
|
|
81
|
-
rules: { [ruleId]: ["error", ...example.ruleOptions ?? rule.defaultOptions ?? []] }
|
|
82
|
-
};
|
|
83
|
-
await verifyExample(new import_eslint.Linter({ cwd: root }), config, example, root, ruleId);
|
|
84
|
-
} finally {
|
|
85
|
-
parser.clearCaches();
|
|
86
|
-
await (0, import_promises.rm)(root, { recursive: true, force: true });
|
|
87
209
|
}
|
|
210
|
+
} finally {
|
|
211
|
+
exampleParser.clear();
|
|
88
212
|
}
|
|
89
213
|
return spec.examples.length;
|
|
90
214
|
}
|
|
91
215
|
function fixturePath(root, path) {
|
|
92
|
-
const resolved = (0,
|
|
93
|
-
if (!resolved.startsWith(root +
|
|
216
|
+
const resolved = (0, import_node_path2.resolve)(root, path);
|
|
217
|
+
if (!resolved.startsWith(root + import_node_path2.sep)) throw new Error(`unsafe example path: ${path}`);
|
|
94
218
|
return resolved;
|
|
95
219
|
}
|
|
96
220
|
function validateFixtureSyntax(exampleId, file, path) {
|
|
97
221
|
if (!/\.[cm]?[jt]sx?$/u.test(file.path)) return;
|
|
98
222
|
try {
|
|
99
|
-
|
|
223
|
+
parser2.parse(file.source, { filePath: path });
|
|
100
224
|
} catch (error) {
|
|
101
225
|
throw new Error(`${exampleId}: invalid fixture ${file.path}`, { cause: error });
|
|
102
226
|
}
|
package/dist/rule-examples.js
CHANGED
|
@@ -1,68 +1,192 @@
|
|
|
1
1
|
// src/verify-rule-examples.ts
|
|
2
2
|
import { mkdtemp, mkdir, rm, writeFile, readFile, symlink } from "fs/promises";
|
|
3
3
|
import { tmpdir } from "os";
|
|
4
|
-
import { dirname, join, resolve, sep } from "path";
|
|
5
|
-
import * as
|
|
4
|
+
import { dirname as dirname2, join, resolve as resolve2, sep } from "path";
|
|
5
|
+
import * as parser2 from "@typescript-eslint/parser";
|
|
6
6
|
import { Linter } from "eslint";
|
|
7
|
+
|
|
8
|
+
// src/create-example-parser.ts
|
|
9
|
+
import { basename, dirname, resolve } from "path";
|
|
10
|
+
import * as parser from "@typescript-eslint/parser";
|
|
11
|
+
import ts from "typescript";
|
|
12
|
+
var WATCH_DEFAULTS = {
|
|
13
|
+
noEmit: true,
|
|
14
|
+
noUnusedLocals: true,
|
|
15
|
+
noUnusedParameters: true,
|
|
16
|
+
allowJs: true,
|
|
17
|
+
allowNonTsExtensions: true,
|
|
18
|
+
checkJs: true
|
|
19
|
+
};
|
|
20
|
+
var MAX_LIBRARY_FILES = 256;
|
|
21
|
+
var MAX_LIBRARY_BYTES = 16 * 1024 * 1024;
|
|
22
|
+
function createExampleParser() {
|
|
23
|
+
const libraryRoot = ts.sys.realpath?.(dirname(ts.getDefaultLibFilePath({})));
|
|
24
|
+
const cache = /* @__PURE__ */ new Map();
|
|
25
|
+
const registries = /* @__PURE__ */ new Map();
|
|
26
|
+
const counts = { programs: 0, libraryAcquires: 0, libraryHits: 0, nativeFallbacks: 0 };
|
|
27
|
+
let bytes = 0;
|
|
28
|
+
return {
|
|
29
|
+
meta: parser.meta,
|
|
30
|
+
clear() {
|
|
31
|
+
cache.clear();
|
|
32
|
+
registries.clear();
|
|
33
|
+
bytes = 0;
|
|
34
|
+
},
|
|
35
|
+
statistics() {
|
|
36
|
+
return { ...counts, libraryFiles: cache.size, libraryBytes: bytes };
|
|
37
|
+
},
|
|
38
|
+
parseForESLint(code, options) {
|
|
39
|
+
const fallback = () => {
|
|
40
|
+
counts.nativeFallbacks += 1;
|
|
41
|
+
return parser.parseForESLint(code, options);
|
|
42
|
+
};
|
|
43
|
+
if (!Object.hasOwn(ts, "JSDocParsingMode")) return fallback();
|
|
44
|
+
if (typeof options?.project !== "string" || typeof options.filePath !== "string" || options.programs != null || options.projectService || options.extraFileExtensions?.length || options.debugLevel) {
|
|
45
|
+
return fallback();
|
|
46
|
+
}
|
|
47
|
+
const config = resolve(options.tsconfigRootDir ?? process.cwd(), options.project);
|
|
48
|
+
let unrecoverable = false;
|
|
49
|
+
const parsed = ts.getParsedCommandLineOfConfigFile(config, WATCH_DEFAULTS, {
|
|
50
|
+
...ts.sys,
|
|
51
|
+
onUnRecoverableConfigFileDiagnostic() {
|
|
52
|
+
unrecoverable = true;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
if (unrecoverable || parsed === void 0 || parsed.errors.length > 0 || parsed.projectReferences?.length) {
|
|
56
|
+
return fallback();
|
|
57
|
+
}
|
|
58
|
+
const filename = resolve(options.filePath);
|
|
59
|
+
const host = ts.createCompilerHost(parsed.options, true);
|
|
60
|
+
const canonical = (path) => host.getCanonicalFileName(resolve(path));
|
|
61
|
+
if (!parsed.fileNames.some((path) => canonical(path) === canonical(filename))) return fallback();
|
|
62
|
+
const mode = options.jsDocParsingMode === "none" ? ts.JSDocParsingMode.ParseNone : options.jsDocParsingMode === "type-info" ? ts.JSDocParsingMode.ParseForTypeInfo : ts.JSDocParsingMode.ParseAll;
|
|
63
|
+
host.jsDocParsingMode = mode;
|
|
64
|
+
let registry = registries.get(mode);
|
|
65
|
+
if (registry === void 0) {
|
|
66
|
+
registry = ts.createDocumentRegistry(ts.sys.useCaseSensitiveFileNames, process.cwd(), mode);
|
|
67
|
+
registries.set(mode, registry);
|
|
68
|
+
}
|
|
69
|
+
const settingsKey = registry.getKeyForCompilationSettings(parsed.options);
|
|
70
|
+
const read = host.readFile.bind(host);
|
|
71
|
+
host.readFile = (path) => canonical(path) === canonical(filename) ? code : read(path);
|
|
72
|
+
const original = host.getSourceFile.bind(host);
|
|
73
|
+
host.getSourceFile = (path, language, onError, fresh) => {
|
|
74
|
+
if (fresh || libraryRoot === void 0 || host.realpath === void 0 || canonical(path) === canonical(filename) || dirname(resolve(path)) !== libraryRoot || !/^lib(?:\..+)?\.d\.ts$/u.test(basename(path)) || !host.fileExists(path)) {
|
|
75
|
+
return original(path, language, onError, fresh);
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
if (host.realpath(path) !== resolve(path)) return original(path, language, onError, fresh);
|
|
79
|
+
} catch {
|
|
80
|
+
return original(path, language, onError, fresh);
|
|
81
|
+
}
|
|
82
|
+
const text = read(path);
|
|
83
|
+
if (text === void 0) return original(path, language, onError, fresh);
|
|
84
|
+
const version = typeof language === "number" ? language : language.languageVersion;
|
|
85
|
+
const format = typeof language === "number" ? void 0 : language.impliedNodeFormat;
|
|
86
|
+
const key = JSON.stringify([path, settingsKey, version, format, mode]);
|
|
87
|
+
const previous = cache.get(key);
|
|
88
|
+
if (previous?.text === text) {
|
|
89
|
+
counts.libraryHits += 1;
|
|
90
|
+
return previous.source;
|
|
91
|
+
}
|
|
92
|
+
const size = Buffer.byteLength(text);
|
|
93
|
+
if (previous !== void 0 || cache.size >= MAX_LIBRARY_FILES || bytes + size > MAX_LIBRARY_BYTES) {
|
|
94
|
+
return original(path, language, onError, fresh);
|
|
95
|
+
}
|
|
96
|
+
const source = registry.acquireDocument(
|
|
97
|
+
path,
|
|
98
|
+
parsed.options,
|
|
99
|
+
ts.ScriptSnapshot.fromString(text),
|
|
100
|
+
"unchanged-native-library",
|
|
101
|
+
ts.ScriptKind.TS,
|
|
102
|
+
language
|
|
103
|
+
);
|
|
104
|
+
cache.set(key, { text, source });
|
|
105
|
+
bytes += size;
|
|
106
|
+
counts.libraryAcquires += 1;
|
|
107
|
+
return source;
|
|
108
|
+
};
|
|
109
|
+
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
|
|
110
|
+
counts.programs += 1;
|
|
111
|
+
if (program.getSourceFile(filename)?.text !== code) {
|
|
112
|
+
throw new Error("The example compiler did not load the current lint source.");
|
|
113
|
+
}
|
|
114
|
+
return parser.parseForESLint(code, {
|
|
115
|
+
...options,
|
|
116
|
+
project: false,
|
|
117
|
+
projectService: false,
|
|
118
|
+
programs: [program],
|
|
119
|
+
disallowAutomaticSingleRunInference: true
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/verify-rule-examples.ts
|
|
7
126
|
async function verifyRuleExamples(rule, setup = {}) {
|
|
8
127
|
const spec = rule.documentation;
|
|
9
128
|
if (spec === void 0 || !spec.publicExamples.some((item) => item.outcome === "match") || !spec.publicExamples.some((item) => item.outcome === "no-match")) {
|
|
10
129
|
throw new Error("rule requires documented public matching and non-matching examples");
|
|
11
130
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
131
|
+
const exampleParser = createExampleParser();
|
|
132
|
+
try {
|
|
133
|
+
for (const example of spec.examples) {
|
|
134
|
+
const root = await mkdtemp(join(tmpdir(), "sarj-rule-example-"));
|
|
135
|
+
try {
|
|
136
|
+
for (const file of example.files) {
|
|
137
|
+
const path = fixturePath(root, file.path);
|
|
138
|
+
validateFixtureSyntax(example.id, file, path);
|
|
139
|
+
await mkdir(dirname2(path), { recursive: true });
|
|
140
|
+
await writeFile(path, file.source);
|
|
141
|
+
}
|
|
142
|
+
for (const dependency of example.installedDependencies ?? []) {
|
|
143
|
+
if (setup.installedDependencyRoot === void 0) throw new Error(`${example.id}: explicit installed dependency root is required`);
|
|
144
|
+
const packageFile = fixturePath(resolve2(setup.installedDependencyRoot), `node_modules/${dependency.module}/package.json`);
|
|
145
|
+
const metadata = JSON.parse(await readFile(packageFile, "utf8"));
|
|
146
|
+
if (typeof metadata !== "object" || metadata === null || !("name" in metadata) || !("version" in metadata) || metadata.name !== dependency.module || metadata.version !== dependency.version) throw new Error(`${example.id}: installed dependency identity/version mismatch for ${dependency.module}`);
|
|
147
|
+
const destination = fixturePath(root, `node_modules/${dependency.module}`);
|
|
148
|
+
await mkdir(dirname2(destination), { recursive: true });
|
|
149
|
+
await symlink(dirname2(packageFile), destination, process.platform === "win32" ? "junction" : "dir");
|
|
150
|
+
}
|
|
151
|
+
const tsconfig = join(root, "tsconfig.json");
|
|
152
|
+
if (!example.files.some((file) => file.path === "tsconfig.json")) {
|
|
153
|
+
await writeFile(tsconfig, JSON.stringify({ compilerOptions: {
|
|
154
|
+
target: "ESNext",
|
|
155
|
+
module: "ESNext",
|
|
156
|
+
moduleResolution: "Bundler",
|
|
157
|
+
strict: true,
|
|
158
|
+
allowJs: true,
|
|
159
|
+
jsx: "preserve",
|
|
160
|
+
noEmit: true
|
|
161
|
+
}, include: ["**/*"] }));
|
|
162
|
+
}
|
|
163
|
+
const ruleId = `@sarj/${spec.ruleId}`;
|
|
164
|
+
const config = {
|
|
165
|
+
files: ["**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}"],
|
|
166
|
+
languageOptions: { parser: exampleParser, parserOptions: { project: tsconfig, tsconfigRootDir: root, disallowAutomaticSingleRunInference: true } },
|
|
167
|
+
plugins: { "@sarj": { rules: { [spec.ruleId]: rule } } },
|
|
168
|
+
rules: { [ruleId]: ["error", ...example.ruleOptions ?? rule.defaultOptions ?? []] }
|
|
169
|
+
};
|
|
170
|
+
await verifyExample(new Linter({ cwd: root }), config, example, root, ruleId);
|
|
171
|
+
} finally {
|
|
172
|
+
parser2.clearCaches();
|
|
173
|
+
await rm(root, { recursive: true, force: true });
|
|
41
174
|
}
|
|
42
|
-
const ruleId = `@sarj/${spec.ruleId}`;
|
|
43
|
-
const config = {
|
|
44
|
-
files: ["**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}"],
|
|
45
|
-
languageOptions: { parser, parserOptions: { project: tsconfig, tsconfigRootDir: root, disallowAutomaticSingleRunInference: true } },
|
|
46
|
-
plugins: { "@sarj": { rules: { [spec.ruleId]: rule } } },
|
|
47
|
-
rules: { [ruleId]: ["error", ...example.ruleOptions ?? rule.defaultOptions ?? []] }
|
|
48
|
-
};
|
|
49
|
-
await verifyExample(new Linter({ cwd: root }), config, example, root, ruleId);
|
|
50
|
-
} finally {
|
|
51
|
-
parser.clearCaches();
|
|
52
|
-
await rm(root, { recursive: true, force: true });
|
|
53
175
|
}
|
|
176
|
+
} finally {
|
|
177
|
+
exampleParser.clear();
|
|
54
178
|
}
|
|
55
179
|
return spec.examples.length;
|
|
56
180
|
}
|
|
57
181
|
function fixturePath(root, path) {
|
|
58
|
-
const resolved =
|
|
182
|
+
const resolved = resolve2(root, path);
|
|
59
183
|
if (!resolved.startsWith(root + sep)) throw new Error(`unsafe example path: ${path}`);
|
|
60
184
|
return resolved;
|
|
61
185
|
}
|
|
62
186
|
function validateFixtureSyntax(exampleId, file, path) {
|
|
63
187
|
if (!/\.[cm]?[jt]sx?$/u.test(file.path)) return;
|
|
64
188
|
try {
|
|
65
|
-
|
|
189
|
+
parser2.parse(file.source, { filePath: path });
|
|
66
190
|
} catch (error) {
|
|
67
191
|
throw new Error(`${exampleId}: invalid fixture ${file.path}`, { cause: error });
|
|
68
192
|
}
|