@cplieger/deadset-ts 1.2.0 → 2.0.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/src/session.ts CHANGED
@@ -10,8 +10,8 @@ import type {
10
10
  Symbol as TSSymbol,
11
11
  TimingInfo,
12
12
  } from "@typescript/native/unstable/sync";
13
- import { SyntaxKind } from "@typescript/native/unstable/ast";
14
- import type { Identifier, Node, SourceFile } from "@typescript/native/unstable/ast";
13
+ import { isIdentifier } from "@typescript/native/unstable/ast";
14
+ import type { Node, SourceFile } from "@typescript/native/unstable/ast";
15
15
 
16
16
  /**
17
17
  * The compiler the analysis reads through. It is a client over a protocol to a
@@ -102,6 +102,25 @@ export interface ProjectView<Brand> {
102
102
  * resolve.
103
103
  */
104
104
  symbolsAt(handles: readonly Handle<Brand>[]): readonly (TSSymbol | undefined)[];
105
+ /**
106
+ * The symbol one node resolves to, asked for that node alone. The accessor behind
107
+ * it takes one identifier and offers no array overload, so it is one round trip per
108
+ * node and it answers only for the residue a batch left unresolved. A node that is
109
+ * not an identifier has no such answer.
110
+ */
111
+ resolvedSymbolAt(handle: Handle<Brand>): TSSymbol | undefined;
112
+ /**
113
+ * The symbol the value of one shorthand property assignment names, which is the
114
+ * declaration `{ a }` reads; the symbol the property's own name resolves to is the
115
+ * property the literal declares. The handle names the assignment, not its name.
116
+ */
117
+ shorthandValueAt(handle: Handle<Brand>): TSSymbol | undefined;
118
+ /**
119
+ * The symbol one alias names, one link along: an import or export specifier
120
+ * resolves to the declaration it carries forward, or to the next alias of a chain.
121
+ * `undefined` where the symbol is no alias, or where the alias resolves to nothing.
122
+ */
123
+ aliasStepOf(symbol: TSSymbol): TSSymbol | undefined;
105
124
  /**
106
125
  * The node one symbol's declaration handle names, read in this project's program,
107
126
  * or `undefined` where this project's program does not hold that file.
@@ -122,30 +141,18 @@ export interface ProjectView<Brand> {
122
141
  */
123
142
  export type ProjectVisitor<Result> = <Brand>(project: ProjectView<Brand>) => Result;
124
143
 
125
- /**
126
- * The identifiers of one source file, in source order. A file is the unit a batch
127
- * resolves: the walk is local to this process and the resolution is not, so one
128
- * file's identifiers are gathered here and handed to the checker together.
129
- */
130
- export function identifiersOf(file: SourceFile): Identifier[] {
131
- const found: Identifier[] = [];
132
- const visit = (node: Node): void => {
133
- if (node.kind === SyntaxKind.Identifier) {
134
- found.push(node as Identifier);
135
- }
136
- node.forEachChild(visit);
137
- };
138
- file.forEachChild(visit);
139
- return found;
140
- }
141
-
142
144
  function viewOf<Brand>(project: Project): ProjectView<Brand> {
145
+ // The snapshot is never updated, so a project's own file set is the same answer
146
+ // every time it is asked for. It is read once because the question is not free:
147
+ // deciding it consults the metadata of every file the program holds, the compiler's
148
+ // own library files included, and a run asks for the set once per pass.
149
+ let ownFiles: readonly SourceFile[] | undefined;
143
150
  return {
144
151
  configFile: project.configFileName,
145
152
  program: project.program,
146
153
  checker: project.checker,
147
- ownSourceFiles: () =>
148
- project.program
154
+ ownSourceFiles: () => {
155
+ ownFiles ??= project.program
149
156
  .getSourceFileNames()
150
157
  // The metadata decides which files belong to the target, and it is read by
151
158
  // name. Fetching each file first and filtering afterwards costs one round
@@ -158,9 +165,21 @@ function viewOf<Brand>(project: Project): ProjectView<Brand> {
158
165
  );
159
166
  })
160
167
  .map((name) => project.program.getSourceFile(name))
161
- .filter((file): file is SourceFile => file !== undefined),
168
+ .filter((file): file is SourceFile => file !== undefined);
169
+ return ownFiles;
170
+ },
162
171
  handle: (node) => ({ node }),
163
172
  symbolsAt: (handles) => project.checker.getSymbolAtLocation(handles.map(({ node }) => node)),
173
+ resolvedSymbolAt: ({ node }) =>
174
+ isIdentifier(node) ? project.checker.getResolvedSymbol(node) : undefined,
175
+ shorthandValueAt: ({ node }) => project.checker.getShorthandAssignmentValueSymbol(node),
176
+ aliasStepOf: (symbol) => {
177
+ const held = project.checker.getImmediateAliasedSymbol(symbol);
178
+ // An alias that resolves to nothing answers with the checker's own unknown
179
+ // symbol, which declares nothing; a symbol that is no alias answers with
180
+ // nothing at all.
181
+ return held === undefined || held.declarations.length === 0 ? undefined : held;
182
+ },
164
183
  declarationAt: (handle) => {
165
184
  const node = handle.resolve(project);
166
185
  return node === undefined ? undefined : { node };
package/src/version.ts CHANGED
@@ -1,22 +1,7 @@
1
- import manifest from "../package.json" with { type: "json" };
2
-
3
1
  /**
4
2
  * The Contract version this analyzer implements. It is the resolved value of
5
3
  * `contract_version` when no configuration source supplies one, and it moves
6
- * independently of {@link version}.
4
+ * independently of the analyzer's own version, which is the one the host answers
5
+ * from the package the analyzer was installed from.
7
6
  */
8
- export const CONTRACT_VERSION = "1.6.0";
9
-
10
- /**
11
- * This analyzer's own version, which the published package carries rather than
12
- * the source: the release pipeline writes the released version into the manifest
13
- * of the artifact it publishes, so an installed package reports the release it
14
- * came from while a checkout reports the version the manifest holds in git.
15
- *
16
- * It is one function because a version written in more than one place is two
17
- * facts that can disagree: the version verb, the report envelope and anything
18
- * else that names this analyzer read this one.
19
- */
20
- export function version(): string {
21
- return manifest.version;
22
- }
7
+ export const CONTRACT_VERSION = "1.7.0";
@@ -1,24 +0,0 @@
1
- #!/usr/bin/env node
2
- // Node refuses to strip types from a .ts file under node_modules, so the
3
- // TypeScript entry cannot start from an install. A load hook scoped to this
4
- // package's own files supplies the stripped source instead.
5
- // https://nodejs.org/api/typescript.html#type-stripping-in-dependencies
6
- import { readFileSync } from "node:fs";
7
- import { registerHooks, stripTypeScriptTypes } from "node:module";
8
-
9
- const packageRoot = new URL("../", import.meta.url).href;
10
-
11
- registerHooks({
12
- load(url, context, nextLoad) {
13
- if (!url.startsWith(packageRoot) || !url.endsWith(".ts")) {
14
- return nextLoad(url, context);
15
- }
16
- return {
17
- format: "module",
18
- source: stripTypeScriptTypes(readFileSync(new URL(url), "utf8")),
19
- shortCircuit: true,
20
- };
21
- },
22
- });
23
-
24
- await import("./deadset-ts.ts");
package/bin/node-host.ts DELETED
@@ -1,28 +0,0 @@
1
- import { readdirSync, readFileSync, statSync } from "node:fs";
2
- import type { DirectoryEntry, Host, PathKind } from "../src/host.ts";
3
-
4
- /**
5
- * The filesystem of the platform the command runs on.
6
- *
7
- * It is the one module that names the platform: everything the analysis is made of
8
- * takes the filesystem as a parameter, so a consumer compiling the published
9
- * sources needs the compiler's types and nothing else.
10
- */
11
- export function nodeHost(): Host {
12
- return {
13
- workingDirectory: () => process.cwd(),
14
- readFile: (path) => readFileSync(path, "utf8"),
15
- readDirectory: (path): readonly DirectoryEntry[] =>
16
- readdirSync(path, { withFileTypes: true }).map((entry) => ({
17
- name: entry.name,
18
- directory: entry.isDirectory(),
19
- })),
20
- kindOf: (path): PathKind => {
21
- const stat = statSync(path, { throwIfNoEntry: false });
22
- if (stat === undefined) {
23
- return "absent";
24
- }
25
- return stat.isDirectory() ? "directory" : "file";
26
- },
27
- };
28
- }