@aoelite/refpact-ts 0.0.0-stage → 0.1.2

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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -2
  3. package/dist/baseline.d.ts +26 -0
  4. package/dist/baseline.js +65 -0
  5. package/dist/core/index.d.ts +79 -0
  6. package/dist/core/index.js +90 -0
  7. package/dist/core/model.d.ts +340 -0
  8. package/dist/core/model.js +1 -0
  9. package/dist/core/results.d.ts +237 -0
  10. package/dist/core/results.js +1 -0
  11. package/dist/core/wasm.d.ts +7 -0
  12. package/dist/core/wasm.js +67 -0
  13. package/dist/custom.d.ts +129 -0
  14. package/dist/custom.js +217 -0
  15. package/dist/filters.d.ts +73 -0
  16. package/dist/filters.js +39 -0
  17. package/dist/frontend/checks.d.ts +15 -0
  18. package/dist/frontend/checks.js +12 -0
  19. package/dist/frontend/classic.d.ts +3 -0
  20. package/dist/frontend/classic.js +105 -0
  21. package/dist/frontend/compiler.d.ts +93 -0
  22. package/dist/frontend/compiler.js +116 -0
  23. package/dist/frontend/components.d.ts +29 -0
  24. package/dist/frontend/components.js +111 -0
  25. package/dist/frontend/declarations.d.ts +45 -0
  26. package/dist/frontend/declarations.js +158 -0
  27. package/dist/frontend/dependencies.d.ts +53 -0
  28. package/dist/frontend/dependencies.js +337 -0
  29. package/dist/frontend/imports.d.ts +149 -0
  30. package/dist/frontend/imports.js +765 -0
  31. package/dist/frontend/native.d.ts +3 -0
  32. package/dist/frontend/native.js +165 -0
  33. package/dist/frontend/paths.d.ts +10 -0
  34. package/dist/frontend/paths.js +18 -0
  35. package/dist/frontend/project.d.ts +118 -0
  36. package/dist/frontend/project.js +775 -0
  37. package/dist/group.d.ts +74 -0
  38. package/dist/group.js +106 -0
  39. package/dist/index.d.ts +35 -0
  40. package/dist/index.js +27 -0
  41. package/dist/items.d.ts +133 -0
  42. package/dist/items.js +179 -0
  43. package/dist/layers.d.ts +112 -0
  44. package/dist/layers.js +215 -0
  45. package/dist/onion.d.ts +78 -0
  46. package/dist/onion.js +96 -0
  47. package/dist/patterns.d.ts +14 -0
  48. package/dist/patterns.js +15 -0
  49. package/dist/rules.d.ts +408 -0
  50. package/dist/rules.js +655 -0
  51. package/package.json +52 -4
  52. package/wasm/refpact_core.wasm +0 -0
@@ -0,0 +1,158 @@
1
+ /** The named items that a file declares at its top level, for the rules about items. */
2
+ import { SyntaxKind } from "./compiler.js";
3
+ /** The kinds of item a declaration can have. */
4
+ export const ITEM_KINDS = ["function", "class", "interface", "type", "enum", "variable", "namespace"];
5
+ /** The visibilities a declaration can have. */
6
+ export const VISIBILITIES = ["exported", "local"];
7
+ /**
8
+ * The relations that the syntax of a declaration gives: `extends` and `implements` of a class
9
+ * or an interface, the decorators of a class, and the types that a function takes and
10
+ * returns and that a variable has.
11
+ */
12
+ export const TIES = ["extends", "implements", "decorator", "returns", "takes", "has-type"];
13
+ /** The modifiers a declaration can have, in the order of their description. */
14
+ export const MODIFIERS = ["async", "abstract", "declare", "const", "default", "documented"];
15
+ /** All relations of a declaration: the ties, and the types that it extends or implements at any depth. */
16
+ export const RELATIONS = [...TIES, "assignable-to"];
17
+ const K = SyntaxKind;
18
+ const KINDS = new Map([
19
+ [K.FunctionDeclaration, "function"],
20
+ [K.ClassDeclaration, "class"],
21
+ [K.InterfaceDeclaration, "interface"],
22
+ [K.TypeAliasDeclaration, "type"],
23
+ [K.EnumDeclaration, "enum"],
24
+ [K.ModuleDeclaration, "namespace"],
25
+ ]);
26
+ function identifier(node) {
27
+ return node !== undefined && node.kind === K.Identifier ? (node.text ?? node.escapedText) : undefined;
28
+ }
29
+ /** The node that names what a decorator or a heritage type refers to: `a` in `@a()`, `a.B` in `extends a.B`. */
30
+ function named(node) {
31
+ let current = node;
32
+ while (current !== undefined && current.kind === K.CallExpression) {
33
+ current = current.expression;
34
+ }
35
+ return current;
36
+ }
37
+ /**
38
+ * The nodes that name a type in a type, at any depth: `Order` and `Error` in
39
+ * `Result<Order, Error>`, `x` in `typeof x`, and `Order` in `import("./order").Order`.
40
+ */
41
+ function typeNames(type, found) {
42
+ if (type === undefined) {
43
+ return;
44
+ }
45
+ const name = type.kind === K.TypeReference ? type.typeName : type.kind === K.TypeQuery ? type.exprName : type.kind === K.ImportType ? type.qualifier : undefined;
46
+ if (name !== undefined) {
47
+ found.push(name);
48
+ }
49
+ type.forEachChild((child) => typeNames(child, found));
50
+ }
51
+ /** The ties of a signature: the types of its parameters, without `this`, and its return type. */
52
+ function signatureTies(signature, ties) {
53
+ for (const parameter of signature.parameters ?? []) {
54
+ if (identifier(parameter.name) === "this") {
55
+ continue;
56
+ }
57
+ const names = [];
58
+ typeNames(parameter.type, names);
59
+ ties.push(...names.map((node) => ({ relation: "takes", node })));
60
+ }
61
+ const names = [];
62
+ typeNames(signature.type, names);
63
+ ties.push(...names.map((node) => ({ relation: "returns", node })));
64
+ }
65
+ const KEYWORDS = new Map([
66
+ [K.AsyncKeyword, "async"],
67
+ [K.AbstractKeyword, "abstract"],
68
+ [K.DeclareKeyword, "declare"],
69
+ [K.ConstKeyword, "const"],
70
+ [K.DefaultKeyword, "default"],
71
+ ]);
72
+ /**
73
+ * The modifiers of a statement, from its keywords and from the comments before it. `more`
74
+ * are modifiers that the caller found elsewhere, such as the `async` of an arrow function.
75
+ */
76
+ function modifiersOf(statement, sourceFile, more) {
77
+ const found = new Set(more);
78
+ for (const modifier of statement.modifiers ?? []) {
79
+ const known = KEYWORDS.get(modifier.kind);
80
+ if (known !== undefined) {
81
+ found.add(known);
82
+ }
83
+ }
84
+ // The text between the end of the node before and the first token of this one holds its
85
+ // comments.
86
+ if (sourceFile.text.slice(statement.pos, statement.getStart(sourceFile)).includes("/**")) {
87
+ found.add("documented");
88
+ }
89
+ return MODIFIERS.filter((modifier) => found.has(modifier));
90
+ }
91
+ /** The top-level declarations of a file, in the order of the text. */
92
+ export function collectDeclarations(sourceFile) {
93
+ const found = [];
94
+ sourceFile.forEachChild((child) => {
95
+ const node = child;
96
+ const modifiers = node.modifiers ?? [];
97
+ const exported = modifiers.some((modifier) => modifier.kind === K.ExportKeyword);
98
+ if (node.kind === K.VariableStatement) {
99
+ for (const declaration of node.declarationList?.declarations ?? []) {
100
+ const name = identifier(declaration.name);
101
+ if (name !== undefined && declaration.name !== undefined) {
102
+ const ties = [];
103
+ const names = [];
104
+ typeNames(declaration.type, names);
105
+ ties.push(...names.map((node) => ({ relation: "has-type", node })));
106
+ // `const make = (order: Order): Total => ..` takes and returns as a function does.
107
+ const value = declaration.initializer;
108
+ const more = [];
109
+ if (value !== undefined && (value.kind === K.ArrowFunction || value.kind === K.FunctionExpression)) {
110
+ signatureTies(value, ties);
111
+ if ((value.modifiers ?? []).some((modifier) => modifier.kind === K.AsyncKeyword)) {
112
+ more.push("async");
113
+ }
114
+ }
115
+ const list = node.declarationList;
116
+ if (list !== undefined && /^const\b/.test(sourceFile.text.slice(list.getStart(sourceFile)))) {
117
+ more.push("const");
118
+ }
119
+ found.push({ name, kind: "variable", at: declaration.name, whole: declaration, exported, ties, modifiers: modifiersOf(node, sourceFile, more) });
120
+ }
121
+ }
122
+ return;
123
+ }
124
+ const kind = KINDS.get(node.kind);
125
+ if (kind === undefined) {
126
+ return;
127
+ }
128
+ const name = identifier(node.name);
129
+ const isDefault = modifiers.some((modifier) => modifier.kind === K.DefaultKeyword);
130
+ if (name === undefined && !(isDefault && kind === "class") && !(isDefault && kind === "function")) {
131
+ return;
132
+ }
133
+ const ties = [];
134
+ for (const modifier of modifiers) {
135
+ const target = modifier.kind === K.Decorator ? named(modifier.expression) : undefined;
136
+ if (target !== undefined) {
137
+ ties.push({ relation: "decorator", node: target });
138
+ }
139
+ }
140
+ for (const clause of node.heritageClauses ?? []) {
141
+ const relation = clause.token === K.ImplementsKeyword ? "implements" : clause.token === K.ExtendsKeyword ? "extends" : undefined;
142
+ for (const type of clause.types ?? []) {
143
+ // TypeScript 7.0 gives each type of a heritage clause its `expression`. TypeScript 7.1
144
+ // gives a type that is only a type, as in `implements` and in the `extends` of an
145
+ // interface, as a type reference, which names its target in `typeName`.
146
+ const target = named(type.expression ?? type.typeName);
147
+ if (relation !== undefined && target !== undefined) {
148
+ ties.push({ relation, node: target });
149
+ }
150
+ }
151
+ }
152
+ if (kind === "function") {
153
+ signatureTies(node, ties);
154
+ }
155
+ found.push({ name: name ?? "default", kind, at: node.name ?? node, whole: node, exported, ties, modifiers: modifiersOf(node, sourceFile, []) });
156
+ });
157
+ return found;
158
+ }
@@ -0,0 +1,53 @@
1
+ import type { Declaration, Fact, Path } from "../core/index.ts";
2
+ /**
3
+ * The kinds of the references that a `package.json` gives. Only a project that is loaded
4
+ * with `readDependencies` has them.
5
+ *
6
+ * - `dependency`, `dev-dependency`, `peer-dependency` and `optional-dependency`: a package
7
+ * that `dependencies`, `devDependencies`, `peerDependencies` or `optionalDependencies` names.
8
+ * - `transitive-dependency`: a package that the file does not name so, and that one of its
9
+ * `dependencies`, `peerDependencies` or `optionalDependencies` needs.
10
+ * - `transitive-dev-dependency`: a package that only its `devDependencies` need.
11
+ *
12
+ * Each of these references has the properties of its package, when the package is
13
+ * installed: its version and its licence.
14
+ */
15
+ export declare const DEPENDENCY_KINDS: readonly ["dependency", "dev-dependency", "peer-dependency", "optional-dependency", "transitive-dependency", "transitive-dev-dependency"];
16
+ /** One of {@link DEPENDENCY_KINDS}. */
17
+ export type DependencyKind = (typeof DEPENDENCY_KINDS)[number];
18
+ /** The kinds of the dependencies that a `package.json` names itself. */
19
+ export declare const NAMED_DEPENDENCY_KINDS: readonly string[];
20
+ /**
21
+ * The properties of a reference to an installed package, from its own `package.json`:
22
+ *
23
+ * - `version`: its version, such as `4.17.21`.
24
+ * - `license`: its licence as the file writes it, such as `MIT` or `(MIT OR Apache-2.0)`. A
25
+ * package that names no licence has none.
26
+ *
27
+ * A package that is not installed has neither. Two versions of one package are two
28
+ * references.
29
+ */
30
+ export declare const PACKAGE_PROPERTIES: readonly ["version", "license"];
31
+ /** The kind of the item that a `package.json` with a name declares: its package. */
32
+ export declare const PACKAGE_ITEM_KIND = "package";
33
+ /** What the `package.json` files of a project give. */
34
+ export interface DependencyFacts {
35
+ /** Each `package.json` as a unit, with the number of its lines. */
36
+ readonly units: readonly {
37
+ readonly path: Path;
38
+ readonly lines: number;
39
+ }[];
40
+ readonly facts: readonly Fact[];
41
+ /** The package that each `package.json` with a name declares. */
42
+ readonly declarations: readonly Declaration[];
43
+ }
44
+ /**
45
+ * The dependencies of the `package.json` files that the files of a project belong to. Each
46
+ * `package.json` is a unit under its path from the root, and each dependency is a reference
47
+ * from it to the package, at the line of its key.
48
+ *
49
+ * A package that another `package.json` of the project declares is also a reference to that
50
+ * file. A package that a dependency needs is found in the `node_modules` directories, as
51
+ * Node finds it; one that is not installed is not followed.
52
+ */
53
+ export declare function dependencyFacts(root: string, files: readonly string[]): DependencyFacts;
@@ -0,0 +1,337 @@
1
+ /**
2
+ * The dependencies that the `package.json` files of a project name: each is a reference
3
+ * from the `package.json` to a package, so that a rule can say which package may depend on
4
+ * which. This is the one place that reads `package.json` files.
5
+ */
6
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
7
+ import path from "node:path";
8
+ import { exportSegment, packageSegment } from "./paths.js";
9
+ /**
10
+ * The kinds of the references that a `package.json` gives. Only a project that is loaded
11
+ * with `readDependencies` has them.
12
+ *
13
+ * - `dependency`, `dev-dependency`, `peer-dependency` and `optional-dependency`: a package
14
+ * that `dependencies`, `devDependencies`, `peerDependencies` or `optionalDependencies` names.
15
+ * - `transitive-dependency`: a package that the file does not name so, and that one of its
16
+ * `dependencies`, `peerDependencies` or `optionalDependencies` needs.
17
+ * - `transitive-dev-dependency`: a package that only its `devDependencies` need.
18
+ *
19
+ * Each of these references has the properties of its package, when the package is
20
+ * installed: its version and its licence.
21
+ */
22
+ export const DEPENDENCY_KINDS = [
23
+ "dependency",
24
+ "dev-dependency",
25
+ "peer-dependency",
26
+ "optional-dependency",
27
+ "transitive-dependency",
28
+ "transitive-dev-dependency",
29
+ ];
30
+ /** The kinds of the dependencies that a `package.json` names itself. */
31
+ export const NAMED_DEPENDENCY_KINDS = ["dependency", "dev-dependency", "peer-dependency", "optional-dependency"];
32
+ /**
33
+ * The properties of a reference to an installed package, from its own `package.json`:
34
+ *
35
+ * - `version`: its version, such as `4.17.21`.
36
+ * - `license`: its licence as the file writes it, such as `MIT` or `(MIT OR Apache-2.0)`. A
37
+ * package that names no licence has none.
38
+ *
39
+ * A package that is not installed has neither. Two versions of one package are two
40
+ * references.
41
+ */
42
+ export const PACKAGE_PROPERTIES = ["version", "license"];
43
+ /** The kind of the item that a `package.json` with a name declares: its package. */
44
+ export const PACKAGE_ITEM_KIND = "package";
45
+ /** The keys of a `package.json` that name dependencies, with the kind of each. */
46
+ const SECTIONS = [
47
+ ["dependencies", "dependency"],
48
+ ["devDependencies", "dev-dependency"],
49
+ ["peerDependencies", "peer-dependency"],
50
+ ["optionalDependencies", "optional-dependency"],
51
+ ];
52
+ /** The keys whose packages are needed where the package is installed. */
53
+ const INSTALLED = ["dependencies", "peerDependencies", "optionalDependencies"];
54
+ /**
55
+ * The places of the keys of a JSON text: for each key at the top that has an object, the
56
+ * keys of that object. The key `""` has the keys at the top themselves.
57
+ */
58
+ function keyPlaces(text) {
59
+ const found = new Map();
60
+ const record = (section, key, place) => {
61
+ const keys = found.get(section) ?? new Map();
62
+ found.set(section, keys);
63
+ if (!keys.has(key)) {
64
+ keys.set(key, place);
65
+ }
66
+ };
67
+ let depth = 0;
68
+ let section;
69
+ let line = 1;
70
+ let lineStart = 0;
71
+ for (let at = 0; at < text.length; at++) {
72
+ const char = text[at];
73
+ if (char === "\n") {
74
+ line += 1;
75
+ lineStart = at + 1;
76
+ }
77
+ else if (char === "{" || char === "[") {
78
+ depth += 1;
79
+ }
80
+ else if (char === "}" || char === "]") {
81
+ depth -= 1;
82
+ }
83
+ else if (char === '"') {
84
+ let end = at + 1;
85
+ while (end < text.length && text[end] !== '"') {
86
+ end += text[end] === "\\" ? 2 : 1;
87
+ }
88
+ let after = end + 1;
89
+ while (after < text.length && /\s/.test(text[after])) {
90
+ after += 1;
91
+ }
92
+ if (text[after] === ":" && depth <= 2) {
93
+ const key = JSON.parse(text.slice(at, end + 1));
94
+ const place = { line, column: at - lineStart + 1 };
95
+ if (depth === 1) {
96
+ section = key;
97
+ record("", key, place);
98
+ }
99
+ else if (section !== undefined) {
100
+ record(section, key, place);
101
+ }
102
+ }
103
+ at = end;
104
+ }
105
+ }
106
+ return found;
107
+ }
108
+ function read(file) {
109
+ if (!existsSync(file)) {
110
+ return undefined;
111
+ }
112
+ const text = readFileSync(file, "utf8");
113
+ let json;
114
+ try {
115
+ json = JSON.parse(text);
116
+ }
117
+ catch (error) {
118
+ throw new Error(`refpact: cannot read ${file}: ${error.message}`);
119
+ }
120
+ const places = keyPlaces(text);
121
+ const name = typeof json["name"] === "string" ? json["name"] : undefined;
122
+ // A `package.json` that has no name and no dependencies, such as one that only sets `"type"`, describes no package.
123
+ if (name === undefined && SECTIONS.every(([section]) => (places.get(section)?.size ?? 0) === 0)) {
124
+ return undefined;
125
+ }
126
+ return { file, name, lines: text.split("\n").length - (text.endsWith("\n") ? 1 : 0), places, namePlace: places.get("")?.get("name") };
127
+ }
128
+ /** Finds the `package.json` that a file belongs to. */
129
+ class Manifests {
130
+ byDirectory = new Map();
131
+ /** The nearest `package.json` that describes a package, in the directory of the file or above it. */
132
+ of(file) {
133
+ return this.inDirectory(path.dirname(file));
134
+ }
135
+ inDirectory(directory) {
136
+ if (this.byDirectory.has(directory)) {
137
+ return this.byDirectory.get(directory);
138
+ }
139
+ const parent = path.dirname(directory);
140
+ const found = read(path.join(directory, "package.json")) ?? (parent === directory ? undefined : this.inDirectory(parent));
141
+ this.byDirectory.set(directory, found);
142
+ return found;
143
+ }
144
+ }
145
+ /** The packages that are installed, as `node_modules` directories have them. */
146
+ class Installed {
147
+ found = new Map();
148
+ read = new Map();
149
+ /** The directory of the package that code in `from` gets for a name, without links; `undefined` when it is not installed. */
150
+ resolve(from, name) {
151
+ const key = `${from}\u0000${name}`;
152
+ if (this.found.has(key)) {
153
+ return this.found.get(key);
154
+ }
155
+ let found;
156
+ for (let directory = from;; directory = path.dirname(directory)) {
157
+ const candidate = path.join(directory, "node_modules", name);
158
+ if (existsSync(path.join(candidate, "package.json"))) {
159
+ found = realpathSync(candidate);
160
+ break;
161
+ }
162
+ if (path.dirname(directory) === directory) {
163
+ break;
164
+ }
165
+ }
166
+ this.found.set(key, found);
167
+ return found;
168
+ }
169
+ /** What the `package.json` of the package in a directory says. */
170
+ package(directory) {
171
+ let found = this.read.get(directory);
172
+ if (found === undefined) {
173
+ let json = {};
174
+ try {
175
+ json = JSON.parse(readFileSync(path.join(directory, "package.json"), "utf8"));
176
+ }
177
+ catch {
178
+ // A package that cannot be read needs nothing that can be followed, and says nothing.
179
+ }
180
+ const needs = INSTALLED.flatMap((key) => {
181
+ const value = json[key];
182
+ return typeof value === "object" && value !== null ? Object.keys(value) : [];
183
+ });
184
+ // An old form of the licence is an object with the name as its `type`.
185
+ const license = json["license"];
186
+ const named = typeof license === "object" && license !== null ? license["type"] : license;
187
+ const properties = {};
188
+ for (const name of PACKAGE_PROPERTIES) {
189
+ const value = name === "license" ? named : json[name];
190
+ if (typeof value === "string") {
191
+ properties[name] = value;
192
+ }
193
+ }
194
+ found = { needs, properties };
195
+ this.read.set(directory, found);
196
+ }
197
+ return found;
198
+ }
199
+ }
200
+ /**
201
+ * The dependencies of the `package.json` files that the files of a project belong to. Each
202
+ * `package.json` is a unit under its path from the root, and each dependency is a reference
203
+ * from it to the package, at the line of its key.
204
+ *
205
+ * A package that another `package.json` of the project declares is also a reference to that
206
+ * file. A package that a dependency needs is found in the `node_modules` directories, as
207
+ * Node finds it; one that is not installed is not followed.
208
+ */
209
+ export function dependencyFacts(root, files) {
210
+ const manifests = new Manifests();
211
+ const found = new Map();
212
+ for (const file of files) {
213
+ const manifest = manifests.of(file);
214
+ if (manifest !== undefined) {
215
+ found.set(manifest.file, manifest);
216
+ }
217
+ }
218
+ const unitOf = (manifest) => path.relative(root, manifest.file).split(path.sep);
219
+ const ordered = [...found.values()].sort((a, b) => {
220
+ const [left, right] = [unitOf(a).join("/"), unitOf(b).join("/")];
221
+ return left < right ? -1 : left > right ? 1 : 0;
222
+ });
223
+ const byName = new Map();
224
+ for (const manifest of ordered) {
225
+ if (manifest.name !== undefined && !byName.has(manifest.name)) {
226
+ byName.set(manifest.name, unitOf(manifest));
227
+ }
228
+ }
229
+ const installed = new Installed();
230
+ const facts = [];
231
+ const declarations = [];
232
+ for (const manifest of ordered) {
233
+ const unit = unitOf(manifest);
234
+ const file = unit.join("/");
235
+ // `at` is the directory of the package, when it is installed.
236
+ const add = (kind, name, place, at, note) => {
237
+ const sibling = byName.get(name);
238
+ const properties = at !== undefined ? installed.package(at).properties : {};
239
+ facts.push({
240
+ unit,
241
+ file,
242
+ line: place.line,
243
+ column: place.column,
244
+ tags: [],
245
+ ...(note !== undefined ? { note } : {}),
246
+ what: {
247
+ type: "reference",
248
+ target: [packageSegment(name)],
249
+ aliases: sibling !== undefined && sibling !== unit ? [sibling] : [],
250
+ kind,
251
+ certainty: "exact",
252
+ ...(Object.keys(properties).length > 0 ? { properties } : {}),
253
+ },
254
+ });
255
+ };
256
+ if (manifest.name !== undefined && manifest.namePlace !== undefined) {
257
+ declarations.push({
258
+ path: [...unit, exportSegment(manifest.name)],
259
+ name: manifest.name,
260
+ unit,
261
+ file,
262
+ line: manifest.namePlace.line,
263
+ column: manifest.namePlace.column,
264
+ tags: [],
265
+ kind: PACKAGE_ITEM_KIND,
266
+ visibility: "exported",
267
+ relations: [],
268
+ modifiers: [],
269
+ });
270
+ }
271
+ const named = [];
272
+ for (const [section, kind] of SECTIONS) {
273
+ for (const [name, place] of manifest.places.get(section) ?? []) {
274
+ named.push({ kind, name, place });
275
+ }
276
+ }
277
+ named.sort((a, b) => a.place.line - b.place.line || a.place.column - b.place.column);
278
+ const directory = path.dirname(manifest.file);
279
+ for (const { kind, name, place } of named) {
280
+ add(kind, name, place, installed.resolve(directory, name));
281
+ }
282
+ // What the dependencies need, on the shortest way first. A package is found on the way
283
+ // that says most: first through what is installed with the package, then through what
284
+ // only its development needs.
285
+ // A package is known by its name and its version: another version of a package that
286
+ // the file names is another package. One that is not installed has no version, and
287
+ // then the name stands for all of them, as the name of the own package does.
288
+ const known = new Set();
289
+ const know = (name, at) => {
290
+ known.add(at !== undefined ? `${name}@${installed.package(at).properties["version"] ?? ""}` : name);
291
+ };
292
+ const isKnown = (name, at) => known.has(name) || known.has(`${name}@${installed.package(at).properties["version"] ?? ""}`);
293
+ for (const { kind, name } of named) {
294
+ if (kind !== "dev-dependency") {
295
+ know(name, installed.resolve(directory, name));
296
+ }
297
+ }
298
+ if (manifest.name !== undefined) {
299
+ known.add(manifest.name);
300
+ }
301
+ const walk = (development) => {
302
+ const kind = development ? "transitive-dev-dependency" : "transitive-dependency";
303
+ const seen = new Set([realpathSync(directory)]);
304
+ const queue = [];
305
+ for (const { kind: of, name, place } of named) {
306
+ const at = (of === "dev-dependency") === development ? installed.resolve(directory, name) : undefined;
307
+ if (at !== undefined && !seen.has(at)) {
308
+ seen.add(at);
309
+ queue.push({ at, place, through: [name] });
310
+ }
311
+ }
312
+ for (let next = 0; next < queue.length; next++) {
313
+ const { at, place, through } = queue[next];
314
+ for (const name of installed.package(at).needs) {
315
+ const needed = installed.resolve(at, name);
316
+ if (needed === undefined || seen.has(needed)) {
317
+ continue;
318
+ }
319
+ seen.add(needed);
320
+ if (!isKnown(name, needed)) {
321
+ know(name, needed);
322
+ add(kind, name, place, needed, `through ${through.map((one) => `\`${one}\``).join(", ")}`);
323
+ }
324
+ queue.push({ at: needed, place, through: [...through, name] });
325
+ }
326
+ }
327
+ };
328
+ walk(false);
329
+ for (const { kind, name } of named) {
330
+ if (kind === "dev-dependency") {
331
+ know(name, installed.resolve(directory, name));
332
+ }
333
+ }
334
+ walk(true);
335
+ }
336
+ return { units: ordered.map((manifest) => ({ path: unitOf(manifest), lines: manifest.lines })), facts, declarations };
337
+ }