ox-dep-lint 0.2.0 → 0.4.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/CHANGELOG.md +24 -0
- package/README.md +31 -24
- package/dist/check-BbeuFBNh.mjs +341 -0
- package/dist/cli.d.mts +1 -1
- package/dist/cli.mjs +2 -2
- package/dist/{config-ClNl5rro.d.mts → config-CmWm5RyK.d.mts} +2 -1
- package/dist/{engine-C1qSxYaL.mjs → engine-DmECSDnu.mjs} +552 -153
- package/dist/index.d.mts +17 -5
- package/dist/index.mjs +3 -3
- package/dist/plugin.mjs +11 -11
- package/package.json +3 -2
- package/dist/check-BIxs2Gr0.mjs +0 -168
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { builtinModules } from "node:module";
|
|
2
|
-
import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { existsSync, lstatSync, readFileSync, readdirSync, readlinkSync, realpathSync, statSync } from "node:fs";
|
|
3
3
|
import nodePath from "node:path";
|
|
4
|
+
import * as v from "valibot";
|
|
4
5
|
import { ResolverFactory } from "oxc-resolver";
|
|
5
6
|
import { Visitor, parseSync } from "oxc-parser";
|
|
6
7
|
// src/config.ts
|
|
@@ -8,7 +9,10 @@ const ruleNames = [
|
|
|
8
9
|
"unknown-workspace-package",
|
|
9
10
|
"package-without-kind",
|
|
10
11
|
"package-outside-workspace",
|
|
12
|
+
"directory-without-package",
|
|
11
13
|
"invalid-package",
|
|
14
|
+
"symbolic-link",
|
|
15
|
+
"unreadable-path",
|
|
12
16
|
"forbidden-edge",
|
|
13
17
|
"type-only-edge",
|
|
14
18
|
"relative-import-leaves-package",
|
|
@@ -40,8 +44,22 @@ const defaultFileClasses = [{
|
|
|
40
44
|
],
|
|
41
45
|
policy: "development"
|
|
42
46
|
}];
|
|
43
|
-
/**
|
|
44
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Build output, coverage reports and the caches tools write under a package root (generated, never written by hand),
|
|
49
|
+
* never read unless `output` says otherwise. Shipped code that imports from them is still judged.
|
|
50
|
+
*/
|
|
51
|
+
const defaultOutput = [
|
|
52
|
+
"dist/**",
|
|
53
|
+
"coverage/**",
|
|
54
|
+
".wrangler/**",
|
|
55
|
+
".astro/**",
|
|
56
|
+
".svelte-kit/**",
|
|
57
|
+
".next/**",
|
|
58
|
+
".turbo/**",
|
|
59
|
+
".vercel/**",
|
|
60
|
+
".nuxt/**",
|
|
61
|
+
".output/**"
|
|
62
|
+
];
|
|
45
63
|
const allManifestFields = [
|
|
46
64
|
"dependencies",
|
|
47
65
|
"devDependencies",
|
|
@@ -50,137 +68,367 @@ const allManifestFields = [
|
|
|
50
68
|
];
|
|
51
69
|
/** Identity, for type checking a configuration file. */
|
|
52
70
|
const defineConfig = (config) => config;
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
kind: [
|
|
68
|
-
"packages",
|
|
69
|
-
"mayUse",
|
|
70
|
-
"externals",
|
|
71
|
-
"runtimeModules"
|
|
72
|
-
],
|
|
73
|
-
package: [
|
|
74
|
-
"uses",
|
|
75
|
-
"externals",
|
|
76
|
-
"typeOnlyUses",
|
|
77
|
-
"valueSpecifiers",
|
|
78
|
-
"testSupportFiles"
|
|
79
|
-
],
|
|
80
|
-
fileClass: ["files", "policy"],
|
|
81
|
-
runtimeModules: ["prefixes", "names"],
|
|
82
|
-
imports: [
|
|
83
|
-
"computed",
|
|
84
|
-
"selfByName",
|
|
85
|
-
"unresolved",
|
|
86
|
-
"manifestEdgeFields",
|
|
87
|
-
"requireDeclared",
|
|
88
|
-
"shippedFields",
|
|
89
|
-
"typeOnly"
|
|
90
|
-
],
|
|
91
|
-
baseline: [
|
|
92
|
-
"file",
|
|
93
|
-
"rule",
|
|
94
|
-
"target",
|
|
95
|
-
"reason"
|
|
96
|
-
]
|
|
97
|
-
};
|
|
98
|
-
const choices = {
|
|
99
|
-
computed: [
|
|
71
|
+
const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
72
|
+
const plainObject = v.custom(isPlainObject);
|
|
73
|
+
/** `schema` itself, which fails to type check unless it accepts exactly `Output`. */
|
|
74
|
+
const schemaOf = () => (schema) => schema;
|
|
75
|
+
const filePolicySchema = schemaOf()(v.picklist([
|
|
76
|
+
"shipped",
|
|
77
|
+
"development",
|
|
78
|
+
"unchecked"
|
|
79
|
+
]));
|
|
80
|
+
const manifestFieldSchema = schemaOf()(v.picklist(allManifestFields));
|
|
81
|
+
const ruleNameSchema = schemaOf()(v.picklist(ruleNames));
|
|
82
|
+
/** The values each import policy may take. */
|
|
83
|
+
const policies = {
|
|
84
|
+
computed: schemaOf()(v.picklist([
|
|
100
85
|
"error-in-shipped",
|
|
101
86
|
"error",
|
|
102
87
|
"allow"
|
|
103
|
-
],
|
|
104
|
-
selfByName: ["error", "allow"],
|
|
105
|
-
unresolved: ["error", "ignore"],
|
|
106
|
-
requireDeclared:
|
|
107
|
-
typeOnly: ["relaxed", "as-values"]
|
|
88
|
+
])),
|
|
89
|
+
selfByName: schemaOf()(v.picklist(["error", "allow"])),
|
|
90
|
+
unresolved: schemaOf()(v.picklist(["error", "ignore"])),
|
|
91
|
+
requireDeclared: schemaOf()(v.boolean()),
|
|
92
|
+
typeOnly: schemaOf()(v.picklist(["relaxed", "as-values"]))
|
|
108
93
|
};
|
|
94
|
+
/** 0.2's test for a list of strings. `every` skips the holes of a sparse array, where `v.array` reads `undefined`. */
|
|
109
95
|
const isStringList = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
110
|
-
const
|
|
111
|
-
/**
|
|
112
|
-
const
|
|
113
|
-
/**
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
96
|
+
const stringList = v.custom(isStringList);
|
|
97
|
+
/** A list of manifest fields, holes skipped as in 0.2. */
|
|
98
|
+
const manifestFieldList = v.custom((value) => isStringList(value) && value.every((field) => v.is(manifestFieldSchema, field)));
|
|
99
|
+
/**
|
|
100
|
+
* A value in a message, written as 0.2 wrote it: a template literal, which throws for a Symbol. 0.2 threw that way
|
|
101
|
+
* (exit 2 with the TypeError's message), so the check still does.
|
|
102
|
+
*/
|
|
103
|
+
const asText = (value) => `${value}`;
|
|
104
|
+
/**
|
|
105
|
+
* Every own entry of an object, each value checked by `value`. Not `v.record`, which skips the keys `constructor`,
|
|
106
|
+
* `prototype` and `__proto__` that 0.2 checked. In an issue's path, the entry is its index and then 1.
|
|
107
|
+
*/
|
|
108
|
+
const ownEntries = (value) => v.pipe(plainObject, v.transform((input) => Object.entries(input)), v.array(v.tuple([v.string(), value])));
|
|
109
|
+
/** 0.2's outline test for a list of objects, which skips holes as `every` does. */
|
|
110
|
+
const objectList = v.custom((value) => Array.isArray(value) && value.every(isPlainObject));
|
|
111
|
+
/** The outline (objects where objects belong, lists where lists belong), checked first: nothing else is read until it holds. */
|
|
112
|
+
const outlineSchema = v.pipe(plainObject, v.looseObject({
|
|
113
|
+
kinds: ownEntries(plainObject),
|
|
114
|
+
packages: v.optional(ownEntries(plainObject)),
|
|
115
|
+
imports: v.optional(plainObject),
|
|
116
|
+
runtimeModules: v.optional(plainObject),
|
|
117
|
+
fileClasses: v.optional(objectList),
|
|
118
|
+
baseline: v.optional(objectList)
|
|
119
|
+
}));
|
|
120
|
+
const kindRuleSchema = v.strictObject({
|
|
121
|
+
packages: v.pipe(v.array(v.unknown()), v.minLength(1)),
|
|
122
|
+
mayUse: v.union([v.literal("any"), stringList]),
|
|
123
|
+
externals: v.optional(v.union([v.literal("any"), stringList])),
|
|
124
|
+
runtimeModules: v.optional(v.boolean())
|
|
125
|
+
});
|
|
126
|
+
const packageRuleSchema = v.strictObject({
|
|
127
|
+
uses: v.exactOptional(stringList),
|
|
128
|
+
externals: v.exactOptional(stringList),
|
|
129
|
+
typeOnlyUses: v.exactOptional(stringList),
|
|
130
|
+
valueSpecifiers: v.exactOptional(stringList),
|
|
131
|
+
testSupportFiles: v.exactOptional(stringList)
|
|
132
|
+
});
|
|
133
|
+
const fileClassSchema = v.strictObject({
|
|
134
|
+
files: stringList,
|
|
135
|
+
policy: filePolicySchema
|
|
136
|
+
});
|
|
137
|
+
const runtimeModulesSchema = v.strictObject({
|
|
138
|
+
prefixes: v.exactOptional(stringList),
|
|
139
|
+
names: v.exactOptional(stringList)
|
|
140
|
+
});
|
|
141
|
+
const importPolicySchema = v.strictObject({
|
|
142
|
+
computed: v.optional(policies.computed),
|
|
143
|
+
selfByName: v.optional(policies.selfByName),
|
|
144
|
+
unresolved: v.optional(policies.unresolved),
|
|
145
|
+
manifestEdgeFields: v.optional(manifestFieldList),
|
|
146
|
+
requireDeclared: v.optional(policies.requireDeclared),
|
|
147
|
+
shippedFields: v.optional(manifestFieldList),
|
|
148
|
+
typeOnly: v.optional(policies.typeOnly)
|
|
149
|
+
});
|
|
150
|
+
const baselineEntrySchema = v.strictObject({
|
|
151
|
+
file: v.optional(v.unknown()),
|
|
152
|
+
rule: ruleNameSchema,
|
|
153
|
+
target: v.pipe(v.string(), v.nonEmpty()),
|
|
154
|
+
reason: v.pipe(v.string(), v.check((reason) => reason.trim() !== ""))
|
|
155
|
+
});
|
|
156
|
+
const configSchema = v.strictObject({
|
|
157
|
+
workspaceScopes: stringList,
|
|
158
|
+
kinds: ownEntries(kindRuleSchema),
|
|
159
|
+
packages: v.optional(ownEntries(packageRuleSchema)),
|
|
160
|
+
fileClasses: v.optional(v.array(fileClassSchema)),
|
|
161
|
+
runtimeModules: v.optional(runtimeModulesSchema),
|
|
162
|
+
virtualModules: v.optional(stringList),
|
|
163
|
+
testOnlySpecifiers: v.optional(stringList),
|
|
164
|
+
imports: v.optional(importPolicySchema),
|
|
165
|
+
baseline: v.optional(v.array(baselineEntrySchema)),
|
|
166
|
+
ignore: v.optional(stringList),
|
|
167
|
+
output: v.optional(stringList)
|
|
168
|
+
});
|
|
169
|
+
/** The sections of the messages, in 0.2's order. */
|
|
170
|
+
const section = {
|
|
171
|
+
configurationKeys: 0,
|
|
172
|
+
kinds: 1,
|
|
173
|
+
packages: 2,
|
|
174
|
+
workspaceScopes: 3,
|
|
175
|
+
testOnlySpecifiers: 4,
|
|
176
|
+
virtualModules: 5,
|
|
177
|
+
ignore: 6,
|
|
178
|
+
output: 7,
|
|
179
|
+
runtimeModuleValues: 8,
|
|
180
|
+
importFields: 9,
|
|
181
|
+
fileClasses: 10,
|
|
182
|
+
runtimeModuleKeys: 11,
|
|
183
|
+
importKeys: 12,
|
|
184
|
+
importPolicies: 13,
|
|
185
|
+
baseline: 14
|
|
186
|
+
};
|
|
187
|
+
/** Within a kind: its unknown keys, then these fields, then the kinds it names that do not exist. */
|
|
188
|
+
const kindFieldOrder = [
|
|
189
|
+
"packages",
|
|
190
|
+
"mayUse",
|
|
191
|
+
"runtimeModules",
|
|
192
|
+
"externals"
|
|
193
|
+
];
|
|
194
|
+
const importFieldOrder = ["manifestEdgeFields", "shippedFields"];
|
|
195
|
+
const importPolicyOrder = [
|
|
196
|
+
"computed",
|
|
197
|
+
"selfByName",
|
|
198
|
+
"unresolved",
|
|
199
|
+
"requireDeclared",
|
|
200
|
+
"typeOnly"
|
|
201
|
+
];
|
|
202
|
+
const baselineFieldOrder = [
|
|
203
|
+
"file",
|
|
204
|
+
"rule",
|
|
205
|
+
"target",
|
|
206
|
+
"reason"
|
|
207
|
+
];
|
|
208
|
+
/** The values a policy may take, as its message lists them. */
|
|
209
|
+
const policyValues = (field) => {
|
|
210
|
+
const schema = policies[field];
|
|
211
|
+
return "options" in schema ? schema.options : [true, false];
|
|
212
|
+
};
|
|
213
|
+
/** One message per key of `object` that `entries` does not know, in the object's order. */
|
|
214
|
+
const unknownKeyProblems = (where, object, entries, order) => Object.keys(object).flatMap((key, position) => Object.hasOwn(entries, key) ? [] : [{
|
|
215
|
+
order: [...order, position],
|
|
216
|
+
message: `${where} has the unknown key ${key} (known: ${Object.keys(entries).join(", ")})`
|
|
217
|
+
}]);
|
|
218
|
+
/** For an object whose every value is a list of strings: the unknown keys whose value is not one, in the object's order. */
|
|
219
|
+
const unknownListProblems = (object, entries, order, message) => Object.entries(object).flatMap(([key, value], position) => Object.hasOwn(entries, key) || v.is(stringList, value) ? [] : [{
|
|
220
|
+
order: [...order, position],
|
|
221
|
+
message: message(key)
|
|
222
|
+
}]);
|
|
223
|
+
/** The value an issue's path passes through at `index`, as an object (an entry of `ownEntries` is a pair). */
|
|
224
|
+
const objectAt = (path, index) => {
|
|
225
|
+
const value = path[index]?.value;
|
|
226
|
+
return isPlainObject(value) ? value : {};
|
|
227
|
+
};
|
|
228
|
+
/** The key and value of the `ownEntries` entry at `index` of an issue's path. */
|
|
229
|
+
const entryAt = (path, index) => {
|
|
230
|
+
const pair = path[index]?.value;
|
|
231
|
+
const [name, value] = Array.isArray(pair) ? pair : [];
|
|
232
|
+
return {
|
|
233
|
+
name: String(name),
|
|
234
|
+
value: isPlainObject(value) ? value : {}
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
const outlineMessage = (path) => {
|
|
238
|
+
const field = path[0]?.key;
|
|
239
|
+
if (field === void 0) return "the configuration must be an object";
|
|
240
|
+
if (field === "kinds") return path.length === 1 ? "kinds must map each kind to its rule" : `kind ${entryAt(path, 1).name} must be an object`;
|
|
241
|
+
if (field === "packages") return path.length === 1 ? "packages must map package names to their rules" : `package ${entryAt(path, 1).name} must be an object`;
|
|
242
|
+
if (field === "imports" || field === "runtimeModules") return `${field} must be an object`;
|
|
243
|
+
return `${field === "fileClasses" ? "fileClasses" : "baseline"} must be a list of objects`;
|
|
244
|
+
};
|
|
245
|
+
const kindProblems = (path, unknownKey, holder) => {
|
|
246
|
+
const index = Number(path[1]?.key);
|
|
247
|
+
const { name } = entryAt(path, 1);
|
|
248
|
+
if (unknownKey) return unknownKeyProblems(`kind ${name}`, holder, kindRuleSchema.entries, [
|
|
249
|
+
section.kinds,
|
|
250
|
+
index,
|
|
251
|
+
0
|
|
252
|
+
]);
|
|
253
|
+
const field = String(path[3]?.key);
|
|
254
|
+
const message = {
|
|
255
|
+
packages: `kind ${name} names no packages`,
|
|
256
|
+
mayUse: `kind ${name}: mayUse must be "any" or a list of kinds`,
|
|
257
|
+
runtimeModules: `kind ${name}: runtimeModules must be true or false`,
|
|
258
|
+
externals: `kind ${name}: externals must be "any" or a list of package names`
|
|
259
|
+
}[field];
|
|
260
|
+
return message === void 0 ? [] : [{
|
|
261
|
+
order: [
|
|
262
|
+
section.kinds,
|
|
263
|
+
index,
|
|
264
|
+
kindFieldOrder.indexOf(field) + 1
|
|
265
|
+
],
|
|
266
|
+
message
|
|
267
|
+
}];
|
|
268
|
+
};
|
|
269
|
+
/** The kinds a kind may use that are not kinds: a reference across entries, which no schema of one entry sees. */
|
|
270
|
+
const kindReferenceProblems = (kinds) => {
|
|
271
|
+
const known = new Set(Object.keys(kinds));
|
|
117
272
|
const problems = [];
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
273
|
+
for (const [index, [name, kind]] of Object.entries(kinds).entries()) {
|
|
274
|
+
const mayUse = isPlainObject(kind) ? kind.mayUse : void 0;
|
|
275
|
+
if (!Array.isArray(mayUse)) continue;
|
|
276
|
+
for (const [position, used] of [...mayUse].entries()) if (typeof used !== "string" || !known.has(used)) problems.push({
|
|
277
|
+
order: [
|
|
278
|
+
section.kinds,
|
|
279
|
+
index,
|
|
280
|
+
kindFieldOrder.length + 1,
|
|
281
|
+
position
|
|
282
|
+
],
|
|
283
|
+
message: `kind ${name} may use ${asText(used)}, which is not a kind`
|
|
284
|
+
});
|
|
285
|
+
}
|
|
124
286
|
return problems;
|
|
125
287
|
};
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
288
|
+
const packageProblems = (path, unknownKey, holder) => {
|
|
289
|
+
const index = Number(path[1]?.key);
|
|
290
|
+
const { name, value } = entryAt(path, 1);
|
|
291
|
+
const listMessage = (key) => `package ${name}: ${key} must be a list of strings`;
|
|
292
|
+
if (unknownKey) return [...unknownKeyProblems(`package ${name}`, holder, packageRuleSchema.entries, [
|
|
293
|
+
section.packages,
|
|
294
|
+
index,
|
|
295
|
+
0
|
|
296
|
+
]), ...unknownListProblems(holder, packageRuleSchema.entries, [
|
|
297
|
+
section.packages,
|
|
298
|
+
index,
|
|
299
|
+
1
|
|
300
|
+
], listMessage)];
|
|
301
|
+
const field = String(path[3]?.key);
|
|
302
|
+
return [{
|
|
303
|
+
order: [
|
|
304
|
+
section.packages,
|
|
305
|
+
index,
|
|
306
|
+
1,
|
|
307
|
+
Object.keys(value).indexOf(field)
|
|
308
|
+
],
|
|
309
|
+
message: listMessage(field)
|
|
310
|
+
}];
|
|
311
|
+
};
|
|
312
|
+
const runtimeModulesProblems = (path, unknownKey, holder) => {
|
|
313
|
+
const listMessage = (key) => `runtimeModules.${key} must be a list of strings`;
|
|
314
|
+
if (unknownKey) return [...unknownKeyProblems("runtimeModules", holder, runtimeModulesSchema.entries, [section.runtimeModuleKeys]), ...unknownListProblems(holder, runtimeModulesSchema.entries, [section.runtimeModuleValues], listMessage)];
|
|
315
|
+
const field = String(path[1]?.key);
|
|
316
|
+
return [{
|
|
317
|
+
order: [section.runtimeModuleValues, Object.keys(objectAt(path, 0)).indexOf(field)],
|
|
318
|
+
message: listMessage(field)
|
|
319
|
+
}];
|
|
320
|
+
};
|
|
321
|
+
const importProblems = (path, unknownKey, holder) => {
|
|
322
|
+
if (unknownKey) return unknownKeyProblems("imports", holder, importPolicySchema.entries, [section.importKeys]);
|
|
323
|
+
const field = String(path[1]?.key);
|
|
324
|
+
if (importFieldOrder.includes(field)) return [{
|
|
325
|
+
order: [section.importFields, importFieldOrder.indexOf(field)],
|
|
326
|
+
message: `imports.${field} must be a list of ${allManifestFields.join(", ")}`
|
|
327
|
+
}];
|
|
328
|
+
return [{
|
|
329
|
+
order: [section.importPolicies, importPolicyOrder.indexOf(field)],
|
|
330
|
+
message: `imports.${field} is ${JSON.stringify(path[1]?.value)}; it must be one of ${policyValues(field).join(", ")}`
|
|
331
|
+
}];
|
|
332
|
+
};
|
|
333
|
+
const fileClassProblems = (path, unknownKey, holder) => {
|
|
334
|
+
const index = Number(path[1]?.key);
|
|
335
|
+
if (unknownKey) return unknownKeyProblems(`file class ${index}`, holder, fileClassSchema.entries, [
|
|
336
|
+
section.fileClasses,
|
|
337
|
+
index,
|
|
338
|
+
0
|
|
339
|
+
]);
|
|
340
|
+
return path[2]?.key === "files" ? [{
|
|
341
|
+
order: [
|
|
342
|
+
section.fileClasses,
|
|
343
|
+
index,
|
|
344
|
+
1
|
|
345
|
+
],
|
|
346
|
+
message: `file class ${index}: files must be a list of globs`
|
|
347
|
+
}] : [{
|
|
348
|
+
order: [
|
|
349
|
+
section.fileClasses,
|
|
350
|
+
index,
|
|
351
|
+
2
|
|
352
|
+
],
|
|
353
|
+
message: `file class ${index} has the unknown policy ${String(objectAt(path, 1).policy)}`
|
|
354
|
+
}];
|
|
355
|
+
};
|
|
356
|
+
const baselineProblems = (path, unknownKey, holder) => {
|
|
357
|
+
const index = Number(path[1]?.key);
|
|
358
|
+
const entry = objectAt(path, 1);
|
|
359
|
+
const file = asText(entry.file);
|
|
360
|
+
if (unknownKey) return unknownKeyProblems(`the baseline entry for ${file}`, holder, baselineEntrySchema.entries, [
|
|
361
|
+
section.baseline,
|
|
362
|
+
index,
|
|
363
|
+
0
|
|
364
|
+
]);
|
|
365
|
+
const field = String(path[2]?.key);
|
|
366
|
+
const message = {
|
|
367
|
+
rule: () => `the baseline entry for ${file} names the unknown rule ${String(entry.rule)}`,
|
|
368
|
+
target: () => `the baseline entry for ${asText(entry.rule)} in ${file} names no target, so it would hide every future finding of that rule there`,
|
|
369
|
+
reason: () => `the baseline entry for ${asText(entry.rule)} in ${file} has no reason`
|
|
370
|
+
}[field]?.();
|
|
371
|
+
return message === void 0 ? [] : [{
|
|
372
|
+
order: [
|
|
373
|
+
section.baseline,
|
|
374
|
+
index,
|
|
375
|
+
baselineFieldOrder.indexOf(field)
|
|
376
|
+
],
|
|
377
|
+
message
|
|
378
|
+
}];
|
|
379
|
+
};
|
|
380
|
+
/** The messages for one issue of `configSchema`. */
|
|
381
|
+
const problemsOf = (issue) => {
|
|
382
|
+
const path = issue.path ?? [];
|
|
383
|
+
const unknownKey = issue.type === "strict_object" && issue.expected === "never";
|
|
384
|
+
const at = unknownKey ? path.slice(0, -1) : path;
|
|
385
|
+
const holder = unknownKey ? path.at(-1)?.input : void 0;
|
|
386
|
+
const object = isPlainObject(holder) ? holder : {};
|
|
387
|
+
const field = at[0]?.key;
|
|
388
|
+
switch (field) {
|
|
389
|
+
case void 0: return unknownKey ? unknownKeyProblems("the configuration", object, configSchema.entries, [section.configurationKeys]) : [];
|
|
390
|
+
case "kinds": return kindProblems(path, unknownKey, object);
|
|
391
|
+
case "packages": return packageProblems(path, unknownKey, object);
|
|
392
|
+
case "runtimeModules": return runtimeModulesProblems(path, unknownKey, object);
|
|
393
|
+
case "imports": return importProblems(path, unknownKey, object);
|
|
394
|
+
case "fileClasses": return fileClassProblems(path, unknownKey, object);
|
|
395
|
+
case "baseline": return baselineProblems(path, unknownKey, object);
|
|
396
|
+
case "workspaceScopes": return [{
|
|
397
|
+
order: [section.workspaceScopes],
|
|
398
|
+
message: "workspaceScopes must be a list of scopes"
|
|
399
|
+
}];
|
|
400
|
+
case "testOnlySpecifiers":
|
|
401
|
+
case "virtualModules":
|
|
402
|
+
case "ignore":
|
|
403
|
+
case "output": return [{
|
|
404
|
+
order: [section[field]],
|
|
405
|
+
message: `${field} must be a list of strings`
|
|
406
|
+
}];
|
|
407
|
+
default: return [];
|
|
176
408
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (
|
|
409
|
+
};
|
|
410
|
+
const compareOrders = (left, right) => {
|
|
411
|
+
for (let index = 0; index < Math.max(left.length, right.length); index++) {
|
|
412
|
+
const difference = (left[index] ?? -1) - (right[index] ?? -1);
|
|
413
|
+
if (difference !== 0) return difference;
|
|
182
414
|
}
|
|
183
|
-
return
|
|
415
|
+
return 0;
|
|
416
|
+
};
|
|
417
|
+
/** Problems that make a configuration unusable, as messages: the outline's alone when it does not hold, else every other. */
|
|
418
|
+
const configProblems = (config) => {
|
|
419
|
+
const outline = v.safeParse(outlineSchema, config);
|
|
420
|
+
if (!outline.success) return [...new Set(outline.issues.map((issue) => outlineMessage(issue.path ?? [])))];
|
|
421
|
+
const sparse = ["fileClasses", "baseline"].filter((key) => [...config[key] ?? []].includes(void 0));
|
|
422
|
+
if (sparse.length > 0) return sparse.map((key) => `${key} must be a list of objects`);
|
|
423
|
+
for (const entry of config.baseline ?? []) asText(entry.file);
|
|
424
|
+
const problems = [...(v.safeParse(configSchema, config).issues ?? []).flatMap(problemsOf), ...kindReferenceProblems(config.kinds)].toSorted((left, right) => compareOrders(left.order, right.order));
|
|
425
|
+
const seen = /* @__PURE__ */ new Set();
|
|
426
|
+
return problems.flatMap((problem) => {
|
|
427
|
+
const key = problem.order.join(" ");
|
|
428
|
+
if (seen.has(key)) return [];
|
|
429
|
+
seen.add(key);
|
|
430
|
+
return [problem.message];
|
|
431
|
+
});
|
|
184
432
|
};
|
|
185
433
|
const resolveConfig = (config) => {
|
|
186
434
|
const problems = configProblems(config);
|
|
@@ -270,20 +518,38 @@ const globExpression = (pattern) => {
|
|
|
270
518
|
const matchesGlob = (path, pattern) => globExpression(pattern).test(path);
|
|
271
519
|
/** Whether `path` matches any of `patterns`. */
|
|
272
520
|
const matchesAny = (path, patterns) => patterns.some((pattern) => matchesGlob(path, pattern));
|
|
521
|
+
// src/result.ts
|
|
522
|
+
const success = (value) => ({
|
|
523
|
+
ok: true,
|
|
524
|
+
value
|
|
525
|
+
});
|
|
526
|
+
const failure = (error) => ({
|
|
527
|
+
ok: false,
|
|
528
|
+
error
|
|
529
|
+
});
|
|
530
|
+
/** Runs `operation`, with what it throws as the failure. */
|
|
531
|
+
const attempt = (operation) => {
|
|
532
|
+
try {
|
|
533
|
+
return success(operation());
|
|
534
|
+
} catch (cause) {
|
|
535
|
+
return failure(cause);
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
/** The message of something thrown, which need not be an Error. */
|
|
539
|
+
const messageOf = (cause) => cause instanceof Error ? cause.message : String(cause);
|
|
540
|
+
/** The error code of something thrown by `node:fs` (`EACCES`), or its message when it has none. */
|
|
541
|
+
const codeOf = (cause) => typeof cause === "object" && cause !== null && "code" in cause && typeof cause.code === "string" ? cause.code : messageOf(cause);
|
|
273
542
|
// src/workspace.ts
|
|
274
543
|
/**
|
|
275
544
|
* The workspace as `package.json` files declare it: the root's `workspaces` globs, and each package's real name (never
|
|
276
545
|
* guessed from its directory) and dependency fields.
|
|
277
546
|
*/
|
|
547
|
+
/** The manifest at `path`, or why it cannot be read. */
|
|
278
548
|
const readManifest = (path) => {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
throw new Error(`${path}: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
284
|
-
}
|
|
285
|
-
if (typeof parsed !== "object" || parsed === null) throw new Error(`${path} is not a JSON object`);
|
|
286
|
-
return parsed;
|
|
549
|
+
const parsed = attempt(() => JSON.parse(readFileSync(path, "utf8")));
|
|
550
|
+
if (!parsed.ok) return failure(`${path}: ${messageOf(parsed.error)}`);
|
|
551
|
+
if (typeof parsed.value !== "object" || parsed.value === null) return failure(`${path} is not a JSON object`);
|
|
552
|
+
return success(parsed.value);
|
|
287
553
|
};
|
|
288
554
|
const namesOf = (field) => typeof field === "object" && field !== null && !Array.isArray(field) ? Object.keys(field).toSorted() : [];
|
|
289
555
|
const fieldsOf = (manifest) => ({
|
|
@@ -311,12 +577,9 @@ const workspaceGlobs = (manifest) => {
|
|
|
311
577
|
if (Array.isArray(field)) return field.filter((entry) => typeof entry === "string");
|
|
312
578
|
if (typeof field === "object" && field !== null && "packages" in field && Array.isArray(field.packages)) return field.packages.filter((entry) => typeof entry === "string");
|
|
313
579
|
};
|
|
314
|
-
const workspacesOf = (
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
} catch {
|
|
318
|
-
return [];
|
|
319
|
-
}
|
|
580
|
+
const workspacesOf = (path) => {
|
|
581
|
+
const manifest = readManifest(path);
|
|
582
|
+
return manifest.ok ? workspaceGlobs(manifest.value) ?? [] : [];
|
|
320
583
|
};
|
|
321
584
|
/** Whether `globs` (from the manifest in `candidate`) hold `directory`: one of them matches it or a directory above it. */
|
|
322
585
|
const holds = (candidate, globs, directory) => {
|
|
@@ -345,7 +608,21 @@ const findWorkspaceRoot = (start) => {
|
|
|
345
608
|
if (nodePath.dirname(candidate) === candidate) return root ?? nearest;
|
|
346
609
|
}
|
|
347
610
|
};
|
|
348
|
-
|
|
611
|
+
/** The first of `path`'s directories (repository-relative), itself included, that is a symbolic link. */
|
|
612
|
+
const linkAlong = (root, path) => {
|
|
613
|
+
const parts = path === "" ? [] : path.split("/");
|
|
614
|
+
return parts.map((_, index) => parts.slice(0, index + 1).join("/")).find((prefix) => {
|
|
615
|
+
const status = attempt(() => lstatSync(nodePath.join(root, prefix)));
|
|
616
|
+
return status.ok && status.value.isSymbolicLink();
|
|
617
|
+
});
|
|
618
|
+
};
|
|
619
|
+
/**
|
|
620
|
+
* The directories under `root` that `pattern` matches, skipping `node_modules` and dot directories, as npm and Bun
|
|
621
|
+
* workspaces do. A symbolic link is never followed, not even one the pattern names literally or passes through (a
|
|
622
|
+
* `packages` that is a link), and a directory that cannot be listed is passed over; with `unlisted`, those and the dot
|
|
623
|
+
* directories the pattern would match are collected there, so the caller can report them.
|
|
624
|
+
*/
|
|
625
|
+
const directoriesMatching = (root, pattern, unlisted) => {
|
|
349
626
|
const clean = (pattern.startsWith("!") ? pattern.slice(1) : pattern).replace(/^\.\//u, "").replace(/\/$/u, "");
|
|
350
627
|
const fixed = [];
|
|
351
628
|
for (const segment of clean.split("/")) {
|
|
@@ -358,15 +635,34 @@ const directoriesMatching = (root, pattern) => {
|
|
|
358
635
|
const walk = (relative, depth) => {
|
|
359
636
|
if (matchesGlob(relative, clean)) found.push(relative);
|
|
360
637
|
if (depth >= depthLimit) return;
|
|
361
|
-
const
|
|
362
|
-
|
|
638
|
+
const listed = attempt(() => readdirSync(nodePath.join(root, relative), { withFileTypes: true }));
|
|
639
|
+
if (!listed.ok) {
|
|
640
|
+
unlisted?.unreadable.push({
|
|
641
|
+
directory: relative,
|
|
642
|
+
code: codeOf(listed.error)
|
|
643
|
+
});
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
for (const entry of listed.value) {
|
|
647
|
+
const path = relative === "" ? entry.name : `${relative}/${entry.name}`;
|
|
648
|
+
if (entry.isDirectory() && entry.name !== "node_modules" && !entry.name.startsWith(".")) walk(path, depth + 1);
|
|
649
|
+
else if (entry.isDirectory() && entry.name.startsWith(".") && entry.name !== ".git" && matchesGlob(path, clean)) unlisted?.dotDirectories.push(path);
|
|
650
|
+
else if (entry.isSymbolicLink() && matchesGlob(path, clean)) unlisted?.links.push(path);
|
|
651
|
+
}
|
|
363
652
|
};
|
|
653
|
+
const link = linkAlong(root, base);
|
|
654
|
+
if (link !== void 0) {
|
|
655
|
+
unlisted?.links.push(link);
|
|
656
|
+
return [];
|
|
657
|
+
}
|
|
364
658
|
if (base === clean) return existsSync(nodePath.join(root, base)) ? [base] : [];
|
|
365
659
|
if (existsSync(nodePath.join(root, base)) && statSync(nodePath.join(root, base)).isDirectory()) walk(base, base === "" ? 0 : base.split("/").length);
|
|
366
660
|
return found;
|
|
367
661
|
};
|
|
368
662
|
const loadWorkspace = (root) => {
|
|
369
|
-
const
|
|
663
|
+
const rootRead = readManifest(nodePath.join(root, "package.json"));
|
|
664
|
+
if (!rootRead.ok) throw new Error(rootRead.error);
|
|
665
|
+
const rootManifest = rootRead.value;
|
|
370
666
|
const globs = workspaceGlobs(rootManifest) ?? [];
|
|
371
667
|
const directories = /* @__PURE__ */ new Set();
|
|
372
668
|
for (const pattern of globs.filter((glob) => !glob.startsWith("!"))) for (const directory of directoriesMatching(root, pattern)) if (existsSync(nodePath.join(root, directory, "package.json"))) directories.add(directory);
|
|
@@ -375,16 +671,15 @@ const loadWorkspace = (root) => {
|
|
|
375
671
|
const problems = [];
|
|
376
672
|
const byName = /* @__PURE__ */ new Map();
|
|
377
673
|
for (const directory of [...directories].toSorted()) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
manifest = readManifest(nodePath.join(root, directory, "package.json"));
|
|
381
|
-
} catch (error) {
|
|
674
|
+
const read = readManifest(nodePath.join(root, directory, "package.json"));
|
|
675
|
+
if (!read.ok) {
|
|
382
676
|
problems.push({
|
|
383
677
|
directory,
|
|
384
|
-
message: `package.json cannot be read: ${
|
|
678
|
+
message: `package.json cannot be read: ${read.error}`
|
|
385
679
|
});
|
|
386
680
|
continue;
|
|
387
681
|
}
|
|
682
|
+
const manifest = read.value;
|
|
388
683
|
if (typeof manifest.name !== "string" || manifest.name === "") {
|
|
389
684
|
problems.push({
|
|
390
685
|
directory,
|
|
@@ -414,7 +709,8 @@ const loadWorkspace = (root) => {
|
|
|
414
709
|
packages,
|
|
415
710
|
byName,
|
|
416
711
|
rootManifest: fieldsOf(rootManifest),
|
|
417
|
-
problems
|
|
712
|
+
problems,
|
|
713
|
+
globs
|
|
418
714
|
};
|
|
419
715
|
};
|
|
420
716
|
/** The package whose directory holds the repository-relative `file`, the deepest one when packages nest. */
|
|
@@ -875,6 +1171,99 @@ const reads = (engine, file) => {
|
|
|
875
1171
|
const owner = packageOfFile(engine.workspace, file);
|
|
876
1172
|
return owner !== void 0 && !matchesAny(file.slice(owner.directory.length + 1), engine.config.output);
|
|
877
1173
|
};
|
|
1174
|
+
/** Whether `glob` matches every path under `directory`: it is `**`, or it or a directory above it matches what comes before a trailing `/**`. */
|
|
1175
|
+
const coversDirectory = (glob, directory) => {
|
|
1176
|
+
if (glob === "**") return true;
|
|
1177
|
+
if (!glob.endsWith("/**") || directory === "") return false;
|
|
1178
|
+
const parts = directory.split("/");
|
|
1179
|
+
return parts.some((_, index) => matchesGlob(parts.slice(0, index + 1).join("/"), glob.slice(0, -3)));
|
|
1180
|
+
};
|
|
1181
|
+
/**
|
|
1182
|
+
* Whether `glob` might match a path under `directory`: its leading segments agree with the directory's until a `**`,
|
|
1183
|
+
* and it is longer than the directory. A glob with braces is taken to match, so a doubt counts as a match.
|
|
1184
|
+
*/
|
|
1185
|
+
const mayMatchUnder = (glob, directory) => {
|
|
1186
|
+
if (glob.includes("{")) return true;
|
|
1187
|
+
const globParts = glob.split("/");
|
|
1188
|
+
const directoryParts = directory === "" ? [] : directory.split("/");
|
|
1189
|
+
for (const [index, part] of directoryParts.entries()) {
|
|
1190
|
+
const globPart = globParts[index];
|
|
1191
|
+
if (globPart === void 0) return false;
|
|
1192
|
+
if (globPart === "**") return true;
|
|
1193
|
+
if (!matchesGlob(part, globPart)) return false;
|
|
1194
|
+
}
|
|
1195
|
+
return globParts.length > directoryParts.length;
|
|
1196
|
+
};
|
|
1197
|
+
/** Whether an `ignore` glob covers every file under the repository-relative `directory`. */
|
|
1198
|
+
const ignoresDirectory = (engine, directory) => engine.config.ignore.some((glob) => coversDirectory(glob, directory));
|
|
1199
|
+
/**
|
|
1200
|
+
* Whether the check reads no file under the repository-relative `directory`, so a symbolic link or an unreadable
|
|
1201
|
+
* directory there hides nothing: an `ignore` glob covers it, or (relative to the package that owns it) an `output` glob
|
|
1202
|
+
* or an unchecked file class does, with no earlier class that might match a file in it. A glob covers a directory only
|
|
1203
|
+
* through a trailing `/**`; any doubt counts as read, so the answer errs towards a finding.
|
|
1204
|
+
*/
|
|
1205
|
+
const skipsDirectory = (engine, directory) => {
|
|
1206
|
+
const { config } = engine;
|
|
1207
|
+
if (ignoresDirectory(engine, directory)) return true;
|
|
1208
|
+
const owner = packageOfFile(engine.workspace, `${directory}/`);
|
|
1209
|
+
if (owner === void 0) return true;
|
|
1210
|
+
const relative = directory.slice(owner.directory.length + 1);
|
|
1211
|
+
if (config.output.some((glob) => coversDirectory(glob, relative))) return true;
|
|
1212
|
+
for (const fileClass of config.fileClasses) {
|
|
1213
|
+
if (fileClass.files.some((glob) => coversDirectory(glob, relative))) return fileClass.policy === "unchecked";
|
|
1214
|
+
if (fileClass.policy !== "unchecked" && fileClass.files.some((glob) => mayMatchUnder(glob, relative))) return false;
|
|
1215
|
+
}
|
|
1216
|
+
return false;
|
|
1217
|
+
};
|
|
1218
|
+
/** Directories the walk never enters, wherever they are: dependencies and git's own data. */
|
|
1219
|
+
const passedOver = (name) => name === "node_modules" || name === ".git";
|
|
1220
|
+
/**
|
|
1221
|
+
* Whether a symbolic link at the repository-relative `path` is a finding: always, whatever it points at, unless it lies
|
|
1222
|
+
* in (or is) a directory the check skips on purpose (`node_modules`, `.git`, or one `skipsDirectory` covers).
|
|
1223
|
+
*/
|
|
1224
|
+
const reportsLink = (engine, path) => !path.split("/").some(passedOver) && !skipsDirectory(engine, path);
|
|
1225
|
+
/** A symbolic link the check met and did not follow, at `path` (repository-relative), with the destination it names. */
|
|
1226
|
+
const symbolicLinkFinding = (path, destination) => ({
|
|
1227
|
+
rule: "symbolic-link",
|
|
1228
|
+
file: path,
|
|
1229
|
+
line: 1,
|
|
1230
|
+
column: 0,
|
|
1231
|
+
message: `${path} is a symbolic link to ${destination}, which the check never follows, so what it reaches goes unchecked: replace it with the files themselves, or baseline it`,
|
|
1232
|
+
target: destination
|
|
1233
|
+
});
|
|
1234
|
+
/** A directory the check needed to list, or a source file it needed to read, and could not, with the error code that said why. */
|
|
1235
|
+
const unreadablePathFinding = (path, code, kind = "directory") => ({
|
|
1236
|
+
rule: "unreadable-path",
|
|
1237
|
+
file: path,
|
|
1238
|
+
line: 1,
|
|
1239
|
+
column: 0,
|
|
1240
|
+
message: kind === "directory" ? `${path} cannot be read (${code}), so the files in it cannot be checked: make it readable, or add ${path}/** to ignore` : `${path} cannot be read (${code}), so its imports cannot be checked: make it readable, or add it to ignore`,
|
|
1241
|
+
target: path
|
|
1242
|
+
});
|
|
1243
|
+
/** The destination a symbolic link names, as written. */
|
|
1244
|
+
const linkDestination = (absolute) => {
|
|
1245
|
+
try {
|
|
1246
|
+
return readlinkSync(absolute);
|
|
1247
|
+
} catch {
|
|
1248
|
+
return "an unreadable destination";
|
|
1249
|
+
}
|
|
1250
|
+
};
|
|
1251
|
+
/**
|
|
1252
|
+
* The first symbolic link on the way from `owner`'s directory down to `file` (both repository-relative), the file
|
|
1253
|
+
* itself included. The CLI's walk never follows one, but oxlint does, and lints the files behind it under the link's
|
|
1254
|
+
* path.
|
|
1255
|
+
*/
|
|
1256
|
+
const linkOnPath = (engine, owner, file) => {
|
|
1257
|
+
const parts = file.slice(owner.directory.length + 1).split("/");
|
|
1258
|
+
for (const [index] of parts.entries()) {
|
|
1259
|
+
const path = [owner.directory, ...parts.slice(0, index + 1)].join("/");
|
|
1260
|
+
try {
|
|
1261
|
+
if (lstatSync(nodePath.join(engine.workspace.root, path)).isSymbolicLink()) return path;
|
|
1262
|
+
} catch {
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
878
1267
|
/** The rules that hold for shipped code and not for development files. */
|
|
879
1268
|
const shippedOnlyRules = /* @__PURE__ */ new Set([
|
|
880
1269
|
"runtime-module",
|
|
@@ -903,9 +1292,11 @@ const developmentFileBreaks = (engine, file) => {
|
|
|
903
1292
|
if (known !== void 0 && [...known.read].every(([path, time]) => modifiedTime(nodePath.join(engine.workspace.root, path)) === time)) return known.breaks;
|
|
904
1293
|
const read = /* @__PURE__ */ new Map();
|
|
905
1294
|
const queue = [file];
|
|
1295
|
+
let head = 0;
|
|
906
1296
|
let breaks;
|
|
907
|
-
while (queue.length
|
|
908
|
-
const next = queue
|
|
1297
|
+
while (head < queue.length && breaks === void 0) {
|
|
1298
|
+
const next = queue[head] ?? file;
|
|
1299
|
+
head += 1;
|
|
909
1300
|
const absolute = nodePath.join(engine.workspace.root, next);
|
|
910
1301
|
const time = modifiedTime(absolute);
|
|
911
1302
|
if (read.has(next) || time === void 0) continue;
|
|
@@ -913,7 +1304,12 @@ const developmentFileBreaks = (engine, file) => {
|
|
|
913
1304
|
const directory = nodePath.dirname(next);
|
|
914
1305
|
read.set(directory, modifiedTime(nodePath.join(engine.workspace.root, directory)) ?? -1);
|
|
915
1306
|
const reached = [];
|
|
916
|
-
const
|
|
1307
|
+
const text = attempt(() => readFileSync(absolute, "utf8"));
|
|
1308
|
+
if (!text.ok) {
|
|
1309
|
+
breaks = `${next} cannot be read (${codeOf(text.error)}), so it cannot be judged`;
|
|
1310
|
+
break;
|
|
1311
|
+
}
|
|
1312
|
+
const finding = findingsForFile(engine, next, text.value, { developmentImports: reached }).find((candidate) => shippedOnlyRules.has(candidate.rule));
|
|
917
1313
|
if (finding !== void 0) breaks = `${next === file ? "" : `through ${next}, `}${finding.rule} at line ${finding.line}: ${finding.message}`;
|
|
918
1314
|
queue.push(...reached);
|
|
919
1315
|
}
|
|
@@ -933,7 +1329,10 @@ const findingsForFile = (engine, file, text, options = {}) => {
|
|
|
933
1329
|
const asShipped = developmentImports !== void 0;
|
|
934
1330
|
const { config, workspace } = engine;
|
|
935
1331
|
const owner = packageOfFile(workspace, file);
|
|
936
|
-
if (owner === void 0 || !sourceFile.test(file)
|
|
1332
|
+
if (owner === void 0 || !sourceFile.test(file)) return [];
|
|
1333
|
+
const link = linkOnPath(engine, owner, file);
|
|
1334
|
+
if (link !== void 0) return reportsLink(engine, link) ? [symbolicLinkFinding(link, linkDestination(nodePath.join(workspace.root, link)))] : [];
|
|
1335
|
+
if (!reads(engine, file) && developmentImports === void 0) return [];
|
|
937
1336
|
const packageRelative = file.slice(owner.directory.length + 1);
|
|
938
1337
|
const classified = effectivePolicy(engine, owner, packageRelative);
|
|
939
1338
|
const policy = asShipped ? "shipped" : classified;
|
|
@@ -1064,4 +1463,4 @@ const findingsForFile = (engine, file, text, options = {}) => {
|
|
|
1064
1463
|
const baselineCovers = (entry, finding) => entry.file === finding.file && entry.rule === finding.rule && entry.target === finding.target;
|
|
1065
1464
|
/** `findings` less those the baseline covers. */
|
|
1066
1465
|
const withoutBaselined = (engine, findings) => findings.filter((finding) => !engine.config.baseline.some((entry) => baselineCovers(entry, finding)));
|
|
1067
|
-
export { configProblems as _,
|
|
1466
|
+
export { defineConfig as A, attempt as C, matchesGlob as D, matchesAny as E, configProblems as O, packageOfFile as S, failure as T, importForms as _, effectivePolicy as a, loadWorkspace as b, linkDestination as c, reads as d, reportsLink as f, withoutBaselined as g, unreadablePathFinding as h, edgeRefusal as i, resolveConfig as j, defaultFileClasses as k, passedOver as l, symbolicLinkFinding as m, coversDirectory as n, findingsForFile as o, skipsDirectory as p, createEngine as r, ignoresDirectory as s, baselineCovers as t, policyOf as u, directoriesMatching as v, codeOf as w, packageNameOf as x, findWorkspaceRoot as y };
|