@cplieger/deadset-ts 5.0.0 → 5.1.1

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 (71) hide show
  1. package/README.md +1 -1
  2. package/dist/src/analysis.js +145 -39
  3. package/dist/src/configuration-files.js +108 -4
  4. package/dist/src/conformance-record.js +3 -9
  5. package/dist/src/contextual-uses.js +247 -0
  6. package/dist/src/convention-rows.js +6 -0
  7. package/dist/src/conventions.js +123 -14
  8. package/dist/src/dependencies.js +8 -2
  9. package/dist/src/destructuring.js +6 -6
  10. package/dist/src/entry-points.js +8 -5
  11. package/dist/src/exempt-classes.js +6 -1
  12. package/dist/src/exempt.js +35 -16
  13. package/dist/src/file-facts.js +23 -20
  14. package/dist/src/findings/completion.js +27 -3
  15. package/dist/src/findings/interfaces.js +6 -2
  16. package/dist/src/findings/reads-and-writes.js +53 -9
  17. package/dist/src/findings/unused-declarations.js +40 -1
  18. package/dist/src/generated-file.js +24 -0
  19. package/dist/src/graph.js +1 -1
  20. package/dist/src/interface-satisfaction.js +2 -68
  21. package/dist/src/inventory.js +17 -4
  22. package/dist/src/kinds.js +4 -3
  23. package/dist/src/manifest.js +29 -4
  24. package/dist/src/outside-tests.js +139 -0
  25. package/dist/src/overrides.js +104 -0
  26. package/dist/src/paths.js +17 -0
  27. package/dist/src/query.js +15 -0
  28. package/dist/src/references.js +229 -11
  29. package/dist/src/resolve.js +12 -11
  30. package/dist/src/roots.js +121 -37
  31. package/dist/src/session.js +4 -3
  32. package/dist/src/stores.js +3 -0
  33. package/dist/src/template-field.js +60 -3
  34. package/dist/src/value-flow.js +74 -0
  35. package/dist/src/version.js +1 -1
  36. package/jsr.json +1 -1
  37. package/package.json +2 -2
  38. package/src/analysis.ts +194 -49
  39. package/src/configuration-files.ts +137 -2
  40. package/src/conformance-record.ts +3 -10
  41. package/src/contextual-uses.ts +358 -0
  42. package/src/convention-rows.ts +8 -0
  43. package/src/conventions.ts +161 -13
  44. package/src/dependencies.ts +14 -1
  45. package/src/destructuring.ts +16 -6
  46. package/src/entry-points.ts +16 -3
  47. package/src/exempt-classes.ts +6 -1
  48. package/src/exempt.ts +67 -16
  49. package/src/file-facts.ts +26 -20
  50. package/src/findings/completion.ts +31 -3
  51. package/src/findings/interfaces.ts +9 -2
  52. package/src/findings/reads-and-writes.ts +70 -9
  53. package/src/findings/unused-declarations.ts +51 -1
  54. package/src/generated-file.ts +27 -0
  55. package/src/graph.ts +4 -4
  56. package/src/interface-satisfaction.ts +1 -92
  57. package/src/inventory.ts +19 -4
  58. package/src/kinds.ts +5 -3
  59. package/src/manifest.ts +40 -4
  60. package/src/outside-tests.ts +191 -0
  61. package/src/overrides.ts +136 -0
  62. package/src/paths.ts +18 -0
  63. package/src/query.ts +26 -0
  64. package/src/references.ts +294 -22
  65. package/src/resolve.ts +14 -13
  66. package/src/roots.ts +152 -44
  67. package/src/session.ts +4 -2
  68. package/src/stores.ts +9 -0
  69. package/src/template-field.ts +65 -3
  70. package/src/value-flow.ts +104 -0
  71. package/src/version.ts +1 -1
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * The entry points a package manifest names, which are roots because something
3
3
  * outside the analyzed program reaches them: a consumer importing the package, or a
4
- * shell running its command or one of its scripts.
5
- *
6
- * One call reads one manifest, the one in the directory it is given.
4
+ * shell running its command or one of its scripts. {@link readManifest} reads one
5
+ * directory's manifest, {@link packageEntries} those of the packages below the target root.
7
6
  */
8
- import { isAbsolutePath, joinPath, resolvePath } from "./paths.js";
7
+ import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.js";
9
8
  /** The manifest's own name. */
10
9
  const MANIFEST = "package.json";
11
10
  /** The three members that name an entry point beside `exports` and `bin`. */
@@ -144,3 +143,29 @@ export function readManifest(host, targetRoot) {
144
143
  declaresExports: manifest["exports"] !== undefined,
145
144
  };
146
145
  }
146
+ /**
147
+ * The entry points every package manifest between the target root and each compiler
148
+ * configuration names: each directory holding a `package.json` that a configuration sits
149
+ * in or below, the target root and `known` excluded, whose manifests the run reads
150
+ * otherwise. Each entry is spelled under its manifest's path below the target root, and
151
+ * is published by its own manifest's rule, as the target's own entries are.
152
+ */
153
+ export function packageEntries(host, targetRoot, configFiles, known) {
154
+ const dirs = new Set();
155
+ for (const configFile of configFiles) {
156
+ for (let dir = dirnamePath(resolvePath(targetRoot, configFile)); dir !== targetRoot && relativePath(targetRoot, dir) !== undefined; dir = dirnamePath(dir)) {
157
+ if (!known.has(dir) && host.kindOf(joinPath(dir, MANIFEST)) === "file") {
158
+ dirs.add(dir);
159
+ }
160
+ }
161
+ }
162
+ return [...dirs].sort().flatMap((dir) => {
163
+ const manifest = readManifest(host, dir);
164
+ const where = `${relativePath(targetRoot, dir) ?? dir}/${MANIFEST}`;
165
+ return manifest.entries.map((entry) => ({
166
+ ...entry,
167
+ member: `${where} ${entry.member}`,
168
+ published: entry.published && (!manifest.declaresExports || entry.member.startsWith("exports")),
169
+ }));
170
+ });
171
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * The test files no compiler configuration of a run holds. The runner executes them all
3
+ * the same, so each module one names is a test reference: the own file a relative
4
+ * specifier names, and the declarations that file exports under the names read from it.
5
+ * Such a file is read without a program, so what its body references beyond the modules
6
+ * it names is not known.
7
+ */
8
+ import { isCallExpression, isExportDeclaration, isImportDeclaration, isNamespaceExport, isNamespaceImport, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
9
+ import { RESOLVED_EXTENSIONS } from "./configuration-files.js";
10
+ import { emittedFrom } from "./emit-map.js";
11
+ import { dirnamePath, joinPath, normalizePath, queriedModulePath } from "./paths.js";
12
+ import { renderPosition } from "./position.js";
13
+ import { nameComponent } from "./ref.js";
14
+ /** The paths below the target root one relative specifier may name, written path first. */
15
+ function candidates(dir, specifier) {
16
+ if (!specifier.startsWith("./") && !specifier.startsWith("../")) {
17
+ return [];
18
+ }
19
+ const path = normalizePath(dir === "" ? specifier : `${dir}/${specifier}`);
20
+ return [
21
+ path,
22
+ ...emittedFrom(path, { sourceDirs: [] }),
23
+ ...RESOLVED_EXTENSIONS.map((extension) => path + extension),
24
+ ...RESOLVED_EXTENSIONS.map((extension) => `${path}/index${extension}`),
25
+ ];
26
+ }
27
+ /** The text of a specifier written as literal text, or undefined for any other expression. */
28
+ function literalText(node) {
29
+ return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node))
30
+ ? node.text
31
+ : undefined;
32
+ }
33
+ /**
34
+ * Every module one file names: an import declaration, a re-export, and an `import()` call
35
+ * of literal text anywhere in the file. A namespace form, a star re-export, a bare import
36
+ * and an `import()` evaluate the module.
37
+ */
38
+ function modulesNamed(file) {
39
+ const found = [];
40
+ const name = (specifier, names, evaluates) => {
41
+ const text = literalText(specifier);
42
+ if (text !== undefined) {
43
+ found.push({ specifier, text, names, evaluates });
44
+ }
45
+ };
46
+ for (const statement of file.statements) {
47
+ if (isImportDeclaration(statement)) {
48
+ const clause = statement.importClause;
49
+ const bindings = clause?.namedBindings;
50
+ name(statement.moduleSpecifier, [
51
+ ...(clause?.name === undefined ? [] : ["default"]),
52
+ ...(bindings === undefined || isNamespaceImport(bindings)
53
+ ? []
54
+ : bindings.elements.map((one) => (one.propertyName ?? one.name).text)),
55
+ ], bindings === undefined ? clause?.name === undefined : isNamespaceImport(bindings));
56
+ }
57
+ else if (isExportDeclaration(statement) && statement.moduleSpecifier !== undefined) {
58
+ const clause = statement.exportClause;
59
+ const star = clause === undefined || isNamespaceExport(clause);
60
+ name(statement.moduleSpecifier, star ? [] : clause.elements.map((one) => (one.propertyName ?? one.name).text), star);
61
+ }
62
+ }
63
+ const visit = (node) => {
64
+ if (isCallExpression(node) && node.expression.kind === SyntaxKind.ImportKeyword) {
65
+ const [argument] = node.arguments;
66
+ if (argument !== undefined) {
67
+ name(argument, [], true);
68
+ }
69
+ }
70
+ node.forEachChild(visit);
71
+ };
72
+ file.forEachChild(visit);
73
+ return found;
74
+ }
75
+ /** Each of the given paths below the target root, parsed; one that cannot be read is skipped. */
76
+ export function testFilesAt(host, targetRoot, paths, parse) {
77
+ return paths.flatMap((path) => {
78
+ const fileName = joinPath(targetRoot, path);
79
+ let text;
80
+ try {
81
+ text = host.readFile(fileName);
82
+ }
83
+ catch {
84
+ return [];
85
+ }
86
+ return [{ path, file: parse(fileName, text) }];
87
+ });
88
+ }
89
+ /**
90
+ * The references the given test files make to one project's own files through the
91
+ * modules they name ({@link modulesNamed}). A name read from a module references each
92
+ * top-level declaration the file exports under it; an evaluating form references the
93
+ * file. A query after the path names the file as {@link queriedModulePath} reads it.
94
+ */
95
+ export function outsideTestReferences(tests, held, targetRoot) {
96
+ const fileAt = new Map(held.symbols
97
+ .filter((symbol) => symbol.kind === "file")
98
+ .map((symbol) => [symbol.position.path, symbol.id]));
99
+ // Keyed on the reference's fragment, which spells the export name where the display
100
+ // name of a default export is its declared name.
101
+ const exported = new Map();
102
+ for (const symbol of held.symbols) {
103
+ if (symbol.exported) {
104
+ const fragment = symbol.ref.slice(symbol.ref.lastIndexOf("#") + 1).replace(/:alias$/u, "");
105
+ const key = `${symbol.parent}\u0000${fragment}`;
106
+ exported.set(key, [...(exported.get(key) ?? []), symbol.id]);
107
+ }
108
+ }
109
+ const found = [];
110
+ for (const { path, file } of tests) {
111
+ const from = `${path}#`;
112
+ const dir = dirnamePath(path) === "." ? "" : dirnamePath(path);
113
+ for (const named of modulesNamed(file)) {
114
+ const written = named.text.includes("?") ? queriedModulePath(named.text) : named.text;
115
+ if (written === undefined) {
116
+ continue;
117
+ }
118
+ const fileId = candidates(dir, written)
119
+ .map((one) => fileAt.get(one))
120
+ .find((one) => one !== undefined);
121
+ if (fileId === undefined) {
122
+ continue;
123
+ }
124
+ const position = renderPosition(file, targetRoot, named.specifier.getStart());
125
+ const read = (to, use) => {
126
+ found.push({ from, to, position, use, resolution: "syntax", test: true, unheld: true });
127
+ };
128
+ if (named.evaluates) {
129
+ read(fileId, "evaluation");
130
+ }
131
+ for (const name of named.names) {
132
+ for (const id of exported.get(`${fileId}\u0000${nameComponent(name)}`) ?? []) {
133
+ read(id, "read");
134
+ }
135
+ }
136
+ }
137
+ }
138
+ return found;
139
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * The overrides of the target's class members. A call through the base class, `this`
3
+ * in the base's own body or a value typed as the base, names the base's member and
4
+ * runs the override of whichever subclass the value is, so the base member's
5
+ * declaration references every override as a use of it.
6
+ */
7
+ import { isClassDeclaration, isClassExpression, isExpressionWithTypeArguments, isPropertyAccessExpression, isQualifiedName, SyntaxKind, } from "@typescript/native/unstable/ast";
8
+ import { memberComponent } from "./class-members.js";
9
+ import { nodeKey } from "./inventory.js";
10
+ import { UNANSWERED } from "./query.js";
11
+ /** The member kinds a subclass overrides. */
12
+ const OVERRIDABLE = new Set(["method", "class-member"]);
13
+ /** The node naming the class one `extends` entry names. */
14
+ function nameOf(entry) {
15
+ if (isExpressionWithTypeArguments(entry)) {
16
+ return isPropertyAccessExpression(entry.expression) ? entry.expression.name : entry.expression;
17
+ }
18
+ return isQualifiedName(entry.typeName) ? entry.typeName.right : entry.typeName;
19
+ }
20
+ /** Each class of the target whose `extends` clause names a class, with the name node. */
21
+ function extending(project, held) {
22
+ const found = [];
23
+ for (const file of project.ownSourceFiles()) {
24
+ const visit = (node) => {
25
+ if (isClassDeclaration(node) || isClassExpression(node)) {
26
+ const id = held.declarations.get(nodeKey(file, node));
27
+ const clause = node.heritageClauses?.find((one) => one.token === SyntaxKind.ExtendsKeyword);
28
+ const base = clause?.types[0];
29
+ if (id !== undefined && base !== undefined) {
30
+ found.push({ id, base: nameOf(base) });
31
+ }
32
+ }
33
+ node.forEachChild(visit);
34
+ };
35
+ file.forEachChild(visit);
36
+ }
37
+ return found;
38
+ }
39
+ /**
40
+ * The references each base class member makes to its overrides, for the classes of one
41
+ * project that extend another class of the target. A member a nearer base already
42
+ * overrides is reached through that override. A private member is not overridden.
43
+ * Each reference is placed at the override and made by its file.
44
+ */
45
+ export function overrideReferences(project, held, chains, testFiles) {
46
+ const classes = extending(project, held);
47
+ if (classes.length === 0) {
48
+ return [];
49
+ }
50
+ const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
51
+ const members = new Map();
52
+ for (const symbol of held.symbols) {
53
+ const component = memberComponent(symbol, byId);
54
+ if (component === undefined ||
55
+ !OVERRIDABLE.has(symbol.kind) ||
56
+ symbol.visibility === "private" ||
57
+ symbol.visibility === "private-name") {
58
+ continue;
59
+ }
60
+ const own = members.get(symbol.parent) ?? new Map();
61
+ own.set(component, symbol);
62
+ members.set(symbol.parent, own);
63
+ }
64
+ const bases = new Map();
65
+ const symbols = project.symbolsAt(classes.map((one) => project.handle(one.base)));
66
+ classes.forEach(({ id }, index) => {
67
+ const symbol = symbols[index];
68
+ if (symbol !== undefined && symbol !== UNANSWERED) {
69
+ bases.set(id, chains
70
+ .chainOf(symbol)
71
+ .map((target) => target.id)
72
+ .filter((target) => byId.get(target)?.kind === "class" && target !== id));
73
+ }
74
+ });
75
+ /** The nearest member of one component the class or a class it extends declares. */
76
+ const declaredAbove = (classId, component, seen) => {
77
+ if (seen.has(classId)) {
78
+ return [];
79
+ }
80
+ seen.add(classId);
81
+ const own = members.get(classId)?.get(component);
82
+ if (own !== undefined) {
83
+ return [own.id];
84
+ }
85
+ return (bases.get(classId) ?? []).flatMap((base) => declaredAbove(base, component, seen));
86
+ };
87
+ const found = [];
88
+ for (const [classId, baseIds] of bases) {
89
+ for (const [component, member] of members.get(classId) ?? []) {
90
+ const seen = new Set([classId]);
91
+ for (const base of baseIds.flatMap((one) => declaredAbove(one, component, seen))) {
92
+ found.push({
93
+ from: base,
94
+ to: member.id,
95
+ position: member.position,
96
+ use: "read",
97
+ resolution: "override",
98
+ test: testFiles.has(member.position.path),
99
+ });
100
+ }
101
+ }
102
+ }
103
+ return found;
104
+ }
package/dist/src/paths.js CHANGED
@@ -65,6 +65,23 @@ export function dirnamePath(path) {
65
65
  export function resolvePath(against, path) {
66
66
  return isAbsolutePath(path) ? normalizePath(path) : joinPath(against, path);
67
67
  }
68
+ /**
69
+ * The relative path before the query of a relative module specifier such as
70
+ * `./worker.ts?worker`, the module a bundler loads; undefined for a specifier with no
71
+ * query, one that is not relative, and one whose query has a `raw` parameter, which
72
+ * reads the file as text and so evaluates nothing.
73
+ */
74
+ export function queriedModulePath(specifier) {
75
+ const query = specifier.indexOf("?");
76
+ if (query < 0 || !(specifier.startsWith("./") || specifier.startsWith("../"))) {
77
+ return undefined;
78
+ }
79
+ const raw = specifier
80
+ .slice(query + 1)
81
+ .split("&")
82
+ .some((parameter) => parameter.split("=")[0] === "raw");
83
+ return raw ? undefined : specifier.slice(0, query);
84
+ }
68
85
  /**
69
86
  * One path as it reads below a root, or undefined when it is not below the root.
70
87
  * Both are normalized first, so a root written with a trailing separator or a `.`
package/dist/src/query.js CHANGED
@@ -73,6 +73,21 @@ function symbolLocation(symbol) {
73
73
  function typeLocation(type) {
74
74
  return `type ${String(type.id)}`;
75
75
  }
76
+ /** The property tables over one project's questions. */
77
+ export function propertyTables(queries) {
78
+ const held = new Map();
79
+ return {
80
+ of: (type) => {
81
+ const known = held.get(type.id);
82
+ if (known !== undefined) {
83
+ return { properties: known, asked: false };
84
+ }
85
+ const properties = queries.propertiesOf(type);
86
+ held.set(type.id, properties);
87
+ return { properties, asked: true };
88
+ },
89
+ };
90
+ }
76
91
  /**
77
92
  * The guarded questions over one project's checker. `configFile` names the project
78
93
  * in the log, `batch` sends many questions in one round trip, `log` is the run's,