ox-dep-lint 0.4.0 → 0.5.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 +25 -0
- package/README.md +3 -3
- package/dist/{check-BbeuFBNh.mjs → check-CWi8LUnB.mjs} +36 -29
- package/dist/cli.mjs +2 -2
- package/dist/{engine-DmECSDnu.mjs → engine-CwR59HVO.mjs} +59 -17
- package/dist/index.d.mts +7 -2
- package/dist/index.mjs +2 -2
- package/dist/plugin.mjs +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 57698bb: `invalid-package` now reports a workspace member whose `dependencies`, `devDependencies`, `optionalDependencies` or `peerDependencies` is not an object: a string (`"dependencies": "some string"`), a list, a number, a boolean or `null`. 0.4 read no names from such a field and said nothing, so a forbidden or undeclared edge written there passed unseen. The finding names the field and what it holds, once per field; the member is still checked, as if that field declared nothing. The root `package.json` is not judged this way.
|
|
8
|
+
|
|
9
|
+
This is a minor release rather than a patch because it can fail a check that passed on 0.4 (the rule in `.changeset/README.md`: before 1.0, a change to what the rules report is a minor bump). Fix the field, or baseline the finding by its own target, the member's directory and the field joined by `#`:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
baseline: [
|
|
13
|
+
{
|
|
14
|
+
file: "tools/quality/package.json",
|
|
15
|
+
rule: "invalid-package",
|
|
16
|
+
target: "tools/quality#peerDependencies",
|
|
17
|
+
reason: "...",
|
|
18
|
+
},
|
|
19
|
+
]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Such an entry covers only that field. If the `package.json` later cannot be read or loses its name, which drops the member from the check, that is still reported, with the directory as its target.
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- 57698bb: A path that disappears while the check runs is skipped instead of reported as `unreadable-path`. A test that creates and removes a temporary tree inside a package while the CLI walks it (tsukuri's `tools/quality/.scan-x/...`) no longer fails the check with `ENOENT`. This covers every place the check lists, stats or reads: the walk of each package, the `workspaces` and kind globs, a member's `package.json`, a symbolic link's destination, and a development file that shipped code imports. `ENOTDIR` (a directory on the way replaced by a file) is skipped the same way. A path that exists but cannot be read (`EACCES` and every other error) is still a finding: `unreadable-path`, or `invalid-package` for a member's `package.json`, or `shipped-imports-development-file` for a development file that shipped code imports, or `symbolic-link` (to an unreadable destination) for a link, and the oxlint plugin, which shares these functions, behaves the same. The file count the CLI prints no longer includes files that disappeared.
|
|
27
|
+
|
|
3
28
|
## 0.4.0
|
|
4
29
|
|
|
5
30
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -28,10 +28,10 @@ ox-dep-lint replaces the hand-written module graph checks of three repositories
|
|
|
28
28
|
| `self-import-by-name` | A package importing itself by its own name (off by default). | both |
|
|
29
29
|
| `package-without-kind` | A workspace package that no kind's globs hold. | both |
|
|
30
30
|
| `package-outside-workspace` | A directory a kind names, with a `package.json`, that the root `workspaces` leave out, so nothing in it would be checked. | CLI |
|
|
31
|
-
| `invalid-package` | A workspace member whose `package.json` cannot be read, has no name, or has another member's name.
|
|
31
|
+
| `invalid-package` | A workspace member whose `package.json` cannot be read, has no name, or has another member's name; or one with a dependency field (`dependencies`, `devDependencies`, `optionalDependencies`, `peerDependencies`) that is not an object (`"dependencies": "x"`, a list, a number, `null`), so no edge in it could be read. Such a member is still checked, as if that field declared nothing. A field's finding has the target `<directory>#<field>` (`tools/quality#peerDependencies`), so a baseline entry for it cannot cover the other causes, whose target is the directory. | CLI |
|
|
32
32
|
| `directory-without-package` | A directory a root `workspaces` glob matches that has no `package.json` and lies neither inside nor above a member, so no package owns its files and nothing checks them (tsukuri's "directory in no tier"). Also a dot directory such a glob would match (`packages/.template`), with or without a `package.json`: npm and Bun workspaces never match dot directories, so it is no package. Only the topmost of nested ones is reported; a negated glob (`!tools/legacy` or `!tools/legacy/**`) leaves the directory out. Silence one with `ignore` (`tools/vendored/**`) or the baseline. | CLI |
|
|
33
33
|
| `symbolic-link` | Every symbolic link under a package directory, whatever it points at (a file, a directory, nothing, a cycle) and whatever its name (`.d.ts`, `.md`, `.json`, extensionless); a link a `workspaces` or kind glob matches or names (`packages/x -> ../elsewhere`); and a link a glob passes through (`packages` itself a link). The check never follows one. It skips only links inside directories it skips on purpose: `node_modules`, `.git`, and directories `ignore`, `output` or an unchecked file class cover through a trailing `/**`. | both (the plugin on each file oxlint reaches through a link) |
|
|
34
|
-
| `unreadable-path` | A directory the walk, a `workspaces` glob or a kind glob cannot list, or a source file the check cannot read (`EACCES`), unless the check skips it on purpose. It used to stop the check with exit code 2.
|
|
34
|
+
| `unreadable-path` | A directory the walk, a `workspaces` glob or a kind glob cannot list, or a source file the check cannot read (`EACCES`), unless the check skips it on purpose. It used to stop the check with exit code 2. A path removed while the check runs (`ENOENT`, or `ENOTDIR` when a directory on the way became a file), such as a test's temporary tree, is skipped with no finding. | CLI |
|
|
35
35
|
| `package-cycle` | A cycle between packages through their runtime dependency fields. | CLI |
|
|
36
36
|
| `stale-baseline` | A baseline entry that no finding needs any more, so the baseline can only shrink. | CLI |
|
|
37
37
|
| `parse-error` | A file that does not parse, so its imports cannot be checked. | both |
|
|
@@ -102,7 +102,7 @@ Releases are published from a `v*` tag by `.github/workflows/release.yml`, with
|
|
|
102
102
|
|
|
103
103
|
## Running it
|
|
104
104
|
|
|
105
|
-
The CLI walks every workspace package's directory, dot directories included (`src/.internal/`), skipping only `node_modules` and `.git` and reading nothing under `output`, so generated and gitignored sources and nested repositories are checked too, and runs the whole-graph checks. Tool caches such as `.wrangler` and `.astro` are in the default `output`; for another cache, set `output` to the defaults plus its glob, since `output` replaces the defaults. The CLI never follows a symbolic link: every link is a `symbolic-link` finding, and a directory it cannot list or a file it cannot read is an `unreadable-path` finding. What it still does not report: plain files directly at a workspace glob's level (`packages/x.ts`), and FIFOs and sockets.
|
|
105
|
+
The CLI walks every workspace package's directory, dot directories included (`src/.internal/`), skipping only `node_modules` and `.git` and reading nothing under `output`, so generated and gitignored sources and nested repositories are checked too, and runs the whole-graph checks. Tool caches such as `.wrangler` and `.astro` are in the default `output`; for another cache, set `output` to the defaults plus its glob, since `output` replaces the defaults. The CLI never follows a symbolic link: every link is a `symbolic-link` finding, and a directory it cannot list or a file it cannot read is an `unreadable-path` finding; one that disappears between being listed and being read is skipped. What it still does not report: plain files directly at a workspace glob's level (`packages/x.ts`), and FIFOs and sockets.
|
|
106
106
|
|
|
107
107
|
```sh
|
|
108
108
|
ox-dep-lint # finds ox-dep-lint.config.{ts,mts,js,mjs,json} at the workspace root
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as attempt,
|
|
1
|
+
import { C as attempt, E as vanished, O as matchesGlob, c as linkDestination, d as reads, f as reportsLink, g as withoutBaselined, h as unreadablePathFinding, i as edgeRefusal, l as passedOver, m as symbolicLinkFinding, n as coversDirectory, o as findingsForFile, p as skipsDirectory, r as createEngine, s as ignoresDirectory, t as baselineCovers, v as directoriesMatching, w as codeOf } from "./engine-CwR59HVO.mjs";
|
|
2
2
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
3
|
import nodePath from "node:path";
|
|
4
4
|
// src/graph.ts
|
|
@@ -20,13 +20,16 @@ const graphFindings = (engine) => {
|
|
|
20
20
|
line: 1,
|
|
21
21
|
column: 0
|
|
22
22
|
};
|
|
23
|
-
for (const problem of workspace.problems)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
for (const problem of workspace.problems) {
|
|
24
|
+
const target = problem.field === void 0 ? problem.directory : `${problem.directory}#${problem.field}`;
|
|
25
|
+
findings.push({
|
|
26
|
+
rule: "invalid-package",
|
|
27
|
+
file: `${problem.directory}/package.json`,
|
|
28
|
+
...at,
|
|
29
|
+
message: problem.message,
|
|
30
|
+
target
|
|
31
|
+
});
|
|
32
|
+
}
|
|
30
33
|
const members = /* @__PURE__ */ new Set([...workspace.packages.map((entry) => entry.directory), ...workspace.problems.map((problem) => problem.directory)]);
|
|
31
34
|
const unlistedByKinds = {
|
|
32
35
|
links: [],
|
|
@@ -109,15 +112,6 @@ const isFile = (absolute) => {
|
|
|
109
112
|
return false;
|
|
110
113
|
}
|
|
111
114
|
};
|
|
112
|
-
/** The error code of a directory that cannot be listed, or undefined when it can. */
|
|
113
|
-
const unreadable = (absolute) => {
|
|
114
|
-
try {
|
|
115
|
-
readdirSync(absolute);
|
|
116
|
-
return;
|
|
117
|
-
} catch (cause) {
|
|
118
|
-
return codeOf(cause);
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
115
|
/**
|
|
122
116
|
* What the root's `workspaces` globs (and a kind's globs) match that is no package: a directory without a
|
|
123
117
|
* `package.json`, whose files no package owns and so nothing checks (only the topmost of nested ones); a dot directory,
|
|
@@ -140,15 +134,16 @@ const directoriesWithoutPackage = (engine, members, unlistedByKinds) => {
|
|
|
140
134
|
for (const glob of workspace.globs.filter((pattern) => !pattern.startsWith("!"))) for (const directory of directoriesMatching(workspace.root, glob, unlisted)) if (!candidates.has(directory)) candidates.set(directory, glob);
|
|
141
135
|
const withoutPackage = (directory, glob) => {
|
|
142
136
|
if (above(directory) || !isDirectory(nodePath.join(workspace.root, directory))) return;
|
|
143
|
-
const
|
|
144
|
-
return
|
|
137
|
+
const listed = attempt(() => readdirSync(nodePath.join(workspace.root, directory)));
|
|
138
|
+
if (!listed.ok) return vanished(listed.error) ? void 0 : unreadablePathFinding(directory, codeOf(listed.error));
|
|
139
|
+
return {
|
|
145
140
|
rule: "directory-without-package",
|
|
146
141
|
file: directory,
|
|
147
142
|
line: 1,
|
|
148
143
|
column: 0,
|
|
149
144
|
message: `${directory} matches the workspaces glob ${glob} but has no package.json, so no package owns what it holds and nothing in it is checked: make it a package, remove it, or add ${directory}/** to ignore`,
|
|
150
145
|
target: directory
|
|
151
|
-
}
|
|
146
|
+
};
|
|
152
147
|
};
|
|
153
148
|
const dotDirectory = (directory) => ({
|
|
154
149
|
rule: "directory-without-package",
|
|
@@ -173,7 +168,10 @@ const directoriesWithoutPackage = (engine, members, unlistedByKinds) => {
|
|
|
173
168
|
})),
|
|
174
169
|
...unlisted.links.map((link) => ({
|
|
175
170
|
path: link,
|
|
176
|
-
finding: () =>
|
|
171
|
+
finding: () => {
|
|
172
|
+
const destination = isFile(nodePath.join(workspace.root, link)) ? void 0 : linkDestination(nodePath.join(workspace.root, link));
|
|
173
|
+
return destination === void 0 ? void 0 : symbolicLinkFinding(link, destination);
|
|
174
|
+
}
|
|
177
175
|
}))
|
|
178
176
|
].toSorted((left, right) => left.path.split("/").length - right.path.split("/").length || left.path.localeCompare(right.path));
|
|
179
177
|
const findings = /* @__PURE__ */ new Map();
|
|
@@ -289,7 +287,7 @@ const staleBaseline = (engine, findings) => engine.config.baseline.filter((entry
|
|
|
289
287
|
* other dot directories hold sources too). A walk rather than `git ls-files`: generated and gitignored sources still
|
|
290
288
|
* ship, and a nested repository or submodule inside a package is still part of it. A symbolic link is never followed:
|
|
291
289
|
* every one is a finding, whatever it points at, unless it lies in a directory the check skips on purpose; and so is a
|
|
292
|
-
* directory the walk cannot list.
|
|
290
|
+
* directory the walk cannot list. A path removed between being listed and being read is skipped, with no finding.
|
|
293
291
|
*/
|
|
294
292
|
const walkSources = (engine) => {
|
|
295
293
|
const files = /* @__PURE__ */ new Set();
|
|
@@ -300,7 +298,7 @@ const walkSources = (engine) => {
|
|
|
300
298
|
try {
|
|
301
299
|
entries = readdirSync(nodePath.join(root, relative), { withFileTypes: true });
|
|
302
300
|
} catch (cause) {
|
|
303
|
-
if (!skipsDirectory(engine, relative)) findings.set(relative, unreadablePathFinding(relative, codeOf(cause)));
|
|
301
|
+
if (!vanished(cause) && !skipsDirectory(engine, relative)) findings.set(relative, unreadablePathFinding(relative, codeOf(cause)));
|
|
304
302
|
return;
|
|
305
303
|
}
|
|
306
304
|
for (const entry of entries) {
|
|
@@ -308,7 +306,8 @@ const walkSources = (engine) => {
|
|
|
308
306
|
if (entry.isDirectory()) {
|
|
309
307
|
if (!passedOver(entry.name)) walk(path);
|
|
310
308
|
} else if (entry.isSymbolicLink()) {
|
|
311
|
-
|
|
309
|
+
const destination = reportsLink(engine, path) ? linkDestination(nodePath.join(root, path)) : void 0;
|
|
310
|
+
if (destination !== void 0) findings.set(path, symbolicLinkFinding(path, destination));
|
|
312
311
|
} else if (entry.isFile() && reads(engine, path)) files.add(path);
|
|
313
312
|
}
|
|
314
313
|
};
|
|
@@ -323,17 +322,25 @@ const listSourceFiles = (engine) => [...walkSources(engine).files];
|
|
|
323
322
|
const checkWorkspace = (root, config) => {
|
|
324
323
|
const engine = createEngine(root, config);
|
|
325
324
|
const { files, findings: walked } = walkSources(engine);
|
|
326
|
-
const perFile =
|
|
325
|
+
const perFile = [];
|
|
326
|
+
let checked = 0;
|
|
327
|
+
for (const file of files) {
|
|
327
328
|
const text = attempt(() => readFileSync(nodePath.join(root, file), "utf8"));
|
|
328
|
-
|
|
329
|
-
|
|
329
|
+
if (text.ok) {
|
|
330
|
+
checked += 1;
|
|
331
|
+
perFile.push(...findingsForFile(engine, file, text.value));
|
|
332
|
+
} else if (!vanished(text.error)) {
|
|
333
|
+
checked += 1;
|
|
334
|
+
perFile.push(unreadablePathFinding(file, codeOf(text.error), "file"));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
330
337
|
const all = [
|
|
331
338
|
...walked,
|
|
332
|
-
...perFile,
|
|
339
|
+
...perFile.filter((finding) => finding.rule !== "package-without-kind"),
|
|
333
340
|
...graphFindings(engine)
|
|
334
341
|
];
|
|
335
342
|
return {
|
|
336
|
-
files:
|
|
343
|
+
files: checked,
|
|
337
344
|
findings: [...withoutBaselined(engine, all), ...staleBaseline(engine, all)]
|
|
338
345
|
};
|
|
339
346
|
};
|
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { y as findWorkspaceRoot } from "./engine-
|
|
3
|
-
import { n as formatFinding, t as checkWorkspace } from "./check-
|
|
2
|
+
import { y as findWorkspaceRoot } from "./engine-CwR59HVO.mjs";
|
|
3
|
+
import { n as formatFinding, t as checkWorkspace } from "./check-CWi8LUnB.mjs";
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import nodePath from "node:path";
|
|
6
6
|
import process from "node:process";
|
|
@@ -537,6 +537,15 @@ const attempt = (operation) => {
|
|
|
537
537
|
};
|
|
538
538
|
/** The message of something thrown, which need not be an Error. */
|
|
539
539
|
const messageOf = (cause) => cause instanceof Error ? cause.message : String(cause);
|
|
540
|
+
/**
|
|
541
|
+
* Whether something thrown by `node:fs` says the path is no longer there: `ENOENT`, or `ENOTDIR` when a directory on the
|
|
542
|
+
* way was replaced by a file. A path removed between being listed and being read (a test's temporary tree, an editor's
|
|
543
|
+
* swap file) fails this way, and is skipped rather than reported as unreadable.
|
|
544
|
+
*/
|
|
545
|
+
const vanished = (cause) => {
|
|
546
|
+
const code = codeOf(cause);
|
|
547
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
548
|
+
};
|
|
540
549
|
/** The error code of something thrown by `node:fs` (`EACCES`), or its message when it has none. */
|
|
541
550
|
const codeOf = (cause) => typeof cause === "object" && cause !== null && "code" in cause && typeof cause.code === "string" ? cause.code : messageOf(cause);
|
|
542
551
|
// src/workspace.ts
|
|
@@ -546,12 +555,36 @@ const codeOf = (cause) => typeof cause === "object" && cause !== null && "code"
|
|
|
546
555
|
*/
|
|
547
556
|
/** The manifest at `path`, or why it cannot be read. */
|
|
548
557
|
const readManifest = (path) => {
|
|
549
|
-
const
|
|
550
|
-
if (!
|
|
551
|
-
|
|
558
|
+
const text = attempt(() => readFileSync(path, "utf8"));
|
|
559
|
+
if (!text.ok) return failure({
|
|
560
|
+
message: `${path}: ${messageOf(text.error)}`,
|
|
561
|
+
vanished: vanished(text.error)
|
|
562
|
+
});
|
|
563
|
+
const parsed = attempt(() => JSON.parse(text.value));
|
|
564
|
+
if (!parsed.ok) return failure({
|
|
565
|
+
message: `${path}: ${messageOf(parsed.error)}`,
|
|
566
|
+
vanished: false
|
|
567
|
+
});
|
|
568
|
+
if (typeof parsed.value !== "object" || parsed.value === null) return failure({
|
|
569
|
+
message: `${path} is not a JSON object`,
|
|
570
|
+
vanished: false
|
|
571
|
+
});
|
|
552
572
|
return success(parsed.value);
|
|
553
573
|
};
|
|
554
|
-
|
|
574
|
+
/** Whether a dependency field has the shape npm and Bun require: an object of package names to version ranges. */
|
|
575
|
+
const isRecord = (field) => typeof field === "object" && field !== null && !Array.isArray(field);
|
|
576
|
+
const namesOf = (field) => isRecord(field) ? Object.keys(field).toSorted() : [];
|
|
577
|
+
/**
|
|
578
|
+
* A message for each dependency field of `manifest` that is present and not an object (`"dependencies": "x"`, a list, a
|
|
579
|
+
* number, null). Such a field declares nothing the check can read, so without a finding its edges would pass unseen.
|
|
580
|
+
*/
|
|
581
|
+
const malformedFields = (manifest) => allManifestFields.filter((field) => manifest[field] !== void 0 && !isRecord(manifest[field])).map((field) => {
|
|
582
|
+
const value = manifest[field];
|
|
583
|
+
return {
|
|
584
|
+
field,
|
|
585
|
+
message: `${field} is ${value === null ? "null" : Array.isArray(value) ? "a list" : `a ${typeof value}`}, not an object of package names to version ranges, so no dependency in it is read`
|
|
586
|
+
};
|
|
587
|
+
});
|
|
555
588
|
const fieldsOf = (manifest) => ({
|
|
556
589
|
dependencies: namesOf(manifest.dependencies),
|
|
557
590
|
devDependencies: namesOf(manifest.devDependencies),
|
|
@@ -566,7 +599,7 @@ const aliasesOf = (manifest) => {
|
|
|
566
599
|
manifest.devDependencies,
|
|
567
600
|
manifest.optionalDependencies,
|
|
568
601
|
manifest.peerDependencies
|
|
569
|
-
]) if (
|
|
602
|
+
]) if (isRecord(field)) for (const [name, spec] of Object.entries(field)) {
|
|
570
603
|
const target = typeof spec === "string" ? /^npm:(?<target>@?[^@]+)/u.exec(spec)?.groups?.target : void 0;
|
|
571
604
|
if (target !== void 0) aliases.set(name, target);
|
|
572
605
|
}
|
|
@@ -637,7 +670,7 @@ const directoriesMatching = (root, pattern, unlisted) => {
|
|
|
637
670
|
if (depth >= depthLimit) return;
|
|
638
671
|
const listed = attempt(() => readdirSync(nodePath.join(root, relative), { withFileTypes: true }));
|
|
639
672
|
if (!listed.ok) {
|
|
640
|
-
unlisted?.unreadable.push({
|
|
673
|
+
if (!vanished(listed.error)) unlisted?.unreadable.push({
|
|
641
674
|
directory: relative,
|
|
642
675
|
code: codeOf(listed.error)
|
|
643
676
|
});
|
|
@@ -656,12 +689,13 @@ const directoriesMatching = (root, pattern, unlisted) => {
|
|
|
656
689
|
return [];
|
|
657
690
|
}
|
|
658
691
|
if (base === clean) return existsSync(nodePath.join(root, base)) ? [base] : [];
|
|
659
|
-
|
|
692
|
+
const status = attempt(() => statSync(nodePath.join(root, base)));
|
|
693
|
+
if (status.ok && status.value.isDirectory()) walk(base, base === "" ? 0 : base.split("/").length);
|
|
660
694
|
return found;
|
|
661
695
|
};
|
|
662
696
|
const loadWorkspace = (root) => {
|
|
663
697
|
const rootRead = readManifest(nodePath.join(root, "package.json"));
|
|
664
|
-
if (!rootRead.ok) throw new Error(rootRead.error);
|
|
698
|
+
if (!rootRead.ok) throw new Error(rootRead.error.message);
|
|
665
699
|
const rootManifest = rootRead.value;
|
|
666
700
|
const globs = workspaceGlobs(rootManifest) ?? [];
|
|
667
701
|
const directories = /* @__PURE__ */ new Set();
|
|
@@ -672,10 +706,11 @@ const loadWorkspace = (root) => {
|
|
|
672
706
|
const byName = /* @__PURE__ */ new Map();
|
|
673
707
|
for (const directory of [...directories].toSorted()) {
|
|
674
708
|
const read = readManifest(nodePath.join(root, directory, "package.json"));
|
|
709
|
+
if (!read.ok && read.error.vanished) continue;
|
|
675
710
|
if (!read.ok) {
|
|
676
711
|
problems.push({
|
|
677
712
|
directory,
|
|
678
|
-
message: `package.json cannot be read: ${read.error}`
|
|
713
|
+
message: `package.json cannot be read: ${read.error.message}`
|
|
679
714
|
});
|
|
680
715
|
continue;
|
|
681
716
|
}
|
|
@@ -695,6 +730,11 @@ const loadWorkspace = (root) => {
|
|
|
695
730
|
});
|
|
696
731
|
continue;
|
|
697
732
|
}
|
|
733
|
+
for (const { field, message } of malformedFields(manifest)) problems.push({
|
|
734
|
+
directory,
|
|
735
|
+
message,
|
|
736
|
+
field
|
|
737
|
+
});
|
|
698
738
|
const entry = {
|
|
699
739
|
name: manifest.name,
|
|
700
740
|
directory,
|
|
@@ -1240,13 +1280,11 @@ const unreadablePathFinding = (path, code, kind = "directory") => ({
|
|
|
1240
1280
|
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
1281
|
target: path
|
|
1242
1282
|
});
|
|
1243
|
-
/** The destination a symbolic link names, as written. */
|
|
1283
|
+
/** The destination a symbolic link names, as written, or undefined when the link has been removed since it was seen. */
|
|
1244
1284
|
const linkDestination = (absolute) => {
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
return "an unreadable destination";
|
|
1249
|
-
}
|
|
1285
|
+
const destination = attempt(() => readlinkSync(absolute));
|
|
1286
|
+
if (destination.ok) return destination.value;
|
|
1287
|
+
return vanished(destination.error) ? void 0 : "an unreadable destination";
|
|
1250
1288
|
};
|
|
1251
1289
|
/**
|
|
1252
1290
|
* The first symbolic link on the way from `owner`'s directory down to `file` (both repository-relative), the file
|
|
@@ -1305,6 +1343,7 @@ const developmentFileBreaks = (engine, file) => {
|
|
|
1305
1343
|
read.set(directory, modifiedTime(nodePath.join(engine.workspace.root, directory)) ?? -1);
|
|
1306
1344
|
const reached = [];
|
|
1307
1345
|
const text = attempt(() => readFileSync(absolute, "utf8"));
|
|
1346
|
+
if (!text.ok && vanished(text.error)) continue;
|
|
1308
1347
|
if (!text.ok) {
|
|
1309
1348
|
breaks = `${next} cannot be read (${codeOf(text.error)}), so it cannot be judged`;
|
|
1310
1349
|
break;
|
|
@@ -1331,7 +1370,10 @@ const findingsForFile = (engine, file, text, options = {}) => {
|
|
|
1331
1370
|
const owner = packageOfFile(workspace, file);
|
|
1332
1371
|
if (owner === void 0 || !sourceFile.test(file)) return [];
|
|
1333
1372
|
const link = linkOnPath(engine, owner, file);
|
|
1334
|
-
if (link !== void 0)
|
|
1373
|
+
if (link !== void 0) {
|
|
1374
|
+
const destination = reportsLink(engine, link) ? linkDestination(nodePath.join(workspace.root, link)) : void 0;
|
|
1375
|
+
return destination === void 0 ? [] : [symbolicLinkFinding(link, destination)];
|
|
1376
|
+
}
|
|
1335
1377
|
if (!reads(engine, file) && developmentImports === void 0) return [];
|
|
1336
1378
|
const packageRelative = file.slice(owner.directory.length + 1);
|
|
1337
1379
|
const classified = effectivePolicy(engine, owner, packageRelative);
|
|
@@ -1463,4 +1505,4 @@ const findingsForFile = (engine, file, text, options = {}) => {
|
|
|
1463
1505
|
const baselineCovers = (entry, finding) => entry.file === finding.file && entry.rule === finding.rule && entry.target === finding.target;
|
|
1464
1506
|
/** `findings` less those the baseline covers. */
|
|
1465
1507
|
const withoutBaselined = (engine, findings) => findings.filter((finding) => !engine.config.baseline.some((entry) => baselineCovers(entry, finding)));
|
|
1466
|
-
export {
|
|
1508
|
+
export { defaultFileClasses as A, attempt as C, matchesAny as D, vanished as E, resolveConfig as M, matchesGlob 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, defineConfig as j, configProblems 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 };
|
package/dist/index.d.mts
CHANGED
|
@@ -14,10 +14,15 @@ interface Workspace {
|
|
|
14
14
|
readonly byName: ReadonlyMap<string, WorkspacePackage>;
|
|
15
15
|
/** The root package's own dependency fields. */
|
|
16
16
|
readonly rootManifest: Readonly<Record<ManifestField, readonly string[]>>;
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Members whose package.json is wrong. With no name, a name another member has, or a package.json that cannot be read,
|
|
19
|
+
* the member cannot be checked and is not in `packages`; with a dependency field that is not an object, it is, as if
|
|
20
|
+
* that field declared nothing, and `field` names it.
|
|
21
|
+
*/
|
|
18
22
|
readonly problems: readonly {
|
|
19
23
|
readonly directory: string;
|
|
20
24
|
readonly message: string;
|
|
25
|
+
readonly field?: ManifestField;
|
|
21
26
|
}[];
|
|
22
27
|
/** The root package.json's `workspaces` globs, negations (`!apps/old`) included. */
|
|
23
28
|
readonly globs: readonly string[];
|
|
@@ -138,7 +143,7 @@ interface SourceWalk {
|
|
|
138
143
|
* other dot directories hold sources too). A walk rather than `git ls-files`: generated and gitignored sources still
|
|
139
144
|
* ship, and a nested repository or submodule inside a package is still part of it. A symbolic link is never followed:
|
|
140
145
|
* every one is a finding, whatever it points at, unless it lies in a directory the check skips on purpose; and so is a
|
|
141
|
-
* directory the walk cannot list.
|
|
146
|
+
* directory the walk cannot list. A path removed between being listed and being read is skipped, with no finding.
|
|
142
147
|
*/
|
|
143
148
|
export declare const walkSources: (engine: Engine) => SourceWalk;
|
|
144
149
|
/** The source files `walkSources` finds. */
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { a as graphFindings, i as walkSources, n as formatFinding, o as staleBaseline, r as listSourceFiles, t as checkWorkspace } from "./check-
|
|
1
|
+
import { A as defaultFileClasses, D as matchesAny, M as resolveConfig, O as matchesGlob, S as packageOfFile, _ as importForms, a as effectivePolicy, b as loadWorkspace, d as reads, g as withoutBaselined, j as defineConfig, k as configProblems, o as findingsForFile, r as createEngine, u as policyOf, x as packageNameOf, y as findWorkspaceRoot } from "./engine-CwR59HVO.mjs";
|
|
2
|
+
import { a as graphFindings, i as walkSources, n as formatFinding, o as staleBaseline, r as listSourceFiles, t as checkWorkspace } from "./check-CWi8LUnB.mjs";
|
|
3
3
|
export { checkWorkspace, configProblems, createEngine, defaultFileClasses, defineConfig, effectivePolicy, findWorkspaceRoot, findingsForFile, formatFinding, graphFindings, importForms, listSourceFiles, loadWorkspace, matchesAny, matchesGlob, packageNameOf, packageOfFile, policyOf, reads, resolveConfig, staleBaseline, walkSources, withoutBaselined };
|
package/dist/plugin.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as attempt, T as failure, g as withoutBaselined, o as findingsForFile, r as createEngine, y as findWorkspaceRoot } from "./engine-
|
|
1
|
+
import { C as attempt, T as failure, g as withoutBaselined, o as findingsForFile, r as createEngine, y as findWorkspaceRoot } from "./engine-CwR59HVO.mjs";
|
|
2
2
|
import { statSync } from "node:fs";
|
|
3
3
|
import nodePath from "node:path";
|
|
4
4
|
// src/plugin.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ox-dep-lint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Module dependency graph rules for oxlint: package kinds, outside packages, runtime and test-only modules, undeclared, escaping and computed imports",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Philip B. Krogh",
|