fallow-type-aware 3.14.0 → 3.16.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.
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { run } from "./src/cli.mjs";
3
+ import { assertTypescriptBackendResolvable } from "./src/backend-preflight.mjs";
4
4
 
5
5
  try {
6
+ assertTypescriptBackendResolvable();
7
+ const { run } = await import("./src/cli.mjs");
6
8
  await run({ input: process.stdin, output: process.stdout, args: process.argv.slice(2) });
7
9
  } catch (error) {
8
10
  const message = error instanceof Error ? error.message : String(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fallow-type-aware",
3
- "version": "3.14.0",
3
+ "version": "3.16.0",
4
4
  "description": "Optional TypeScript-Go semantic refinement sidecar for Fallow",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -0,0 +1,42 @@
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+
4
+ import { BACKEND_VERSION } from "./generated-protocol.mjs";
5
+
6
+ const REQUIRED_MAJOR = Number.parseInt(BACKEND_VERSION, 10);
7
+ const INSTALL_HINT =
8
+ "run `npm ci` in tools/type-aware-sidecar so the sidecar resolves its own typescript install";
9
+
10
+ /**
11
+ * Verify that module resolution hands the sidecar a typescript install that
12
+ * provides the `typescript/unstable/sync` backend entry point.
13
+ *
14
+ * The semantic modules import that entry point statically, so an incompatible
15
+ * resolution (for example a typescript 6 hoisted into an ancestor
16
+ * `node_modules`) fails at link time with a bare module path instead of the
17
+ * version conflict that caused it. This preflight runs before those imports
18
+ * and names the resolved version, its location, and the fix. Exact
19
+ * backend-version parity stays enforced in `protocol.mjs`.
20
+ *
21
+ * @param {string | URL} resolveFrom Module URL or file path to resolve from;
22
+ * defaults to this module so the check mirrors the semantic imports.
23
+ */
24
+ export const assertTypescriptBackendResolvable = (resolveFrom = import.meta.url) => {
25
+ const sidecarRequire = createRequire(resolveFrom);
26
+ let manifestPath;
27
+ try {
28
+ manifestPath = sidecarRequire.resolve("typescript/package.json");
29
+ } catch {
30
+ throw new Error(`typescript is not installed; ${INSTALL_HINT}`);
31
+ }
32
+ const { version } = sidecarRequire(manifestPath);
33
+ const major = Number.parseInt(version, 10);
34
+ if (Number.isFinite(major) && major >= REQUIRED_MAJOR) {
35
+ return;
36
+ }
37
+ throw new Error(
38
+ `resolved typescript ${version} from ${path.dirname(manifestPath)}, which lacks the ` +
39
+ `typescript/unstable/sync backend; fallow-type-aware needs typescript ${BACKEND_VERSION}; ` +
40
+ INSTALL_HINT,
41
+ );
42
+ };
@@ -12,9 +12,11 @@ import {
12
12
  isMethodDeclaration,
13
13
  isMethodSignatureDeclaration,
14
14
  isModuleDeclaration,
15
+ isNamespaceExport,
15
16
  isPropertyDeclaration,
16
17
  isPropertySignatureDeclaration,
17
18
  isSetAccessorDeclaration,
19
+ isSourceFile,
18
20
  isTypeAliasDeclaration,
19
21
  isVariableDeclaration,
20
22
  } from "typescript/unstable/ast/is";
@@ -45,7 +47,12 @@ const TYPE_ONLY_DECLARATIONS = [
45
47
  isTypeAliasDeclaration,
46
48
  isPropertySignatureDeclaration,
47
49
  ];
48
- const DUAL_NAMESPACE_DECLARATIONS = [isClassDeclaration, isClassExpression, isEnumDeclaration];
50
+ const DUAL_NAMESPACE_DECLARATIONS = [
51
+ isClassDeclaration,
52
+ isClassExpression,
53
+ isEnumDeclaration,
54
+ isSourceFile,
55
+ ];
49
56
  const DECLARATION_OWNER_NODES = [isClassDeclaration, isClassExpression, isInterfaceDeclaration];
50
57
 
51
58
  const slash = (value) => value.split(path.sep).join("/");
@@ -101,6 +108,8 @@ export const ownerDeclaration = (node) => {
101
108
 
102
109
  export const isDeclaration = (node) => declarationKind(node) !== undefined;
103
110
 
111
+ export const isExportAnchor = (node) => isExportSpecifier(node) || isNamespaceExport(node);
112
+
104
113
  const visit = (node, callback) => {
105
114
  callback(node);
106
115
  node.forEachChild((child) => {
@@ -151,6 +160,16 @@ const indexExportSpecifier = (index, node) => {
151
160
  );
152
161
  };
153
162
 
163
+ const indexNamespaceExport = (index, node) => {
164
+ const exportedName = nodeText(node.name);
165
+ if (!exportedName) return;
166
+ [node.parent, node]
167
+ .flatMap((candidate) => positions(candidate))
168
+ .forEach((position) =>
169
+ indexExportPosition(index, node, { localName: exportedName, exportedName }, position),
170
+ );
171
+ };
172
+
154
173
  const defaultModifierFor = (node) =>
155
174
  node.modifiers?.find((modifier) => modifier.getText(node.getSourceFile()) === "default");
156
175
 
@@ -197,6 +216,10 @@ const declarationIndex = (sourceFile) => {
197
216
  indexExportSpecifier(index, node);
198
217
  return;
199
218
  }
219
+ if (isNamespaceExport(node)) {
220
+ indexNamespaceExport(index, node);
221
+ return;
222
+ }
200
223
  indexDeclarationNode(index, node);
201
224
  });
202
225
  declarationIndexes.set(sourceFile, index);
@@ -205,7 +228,7 @@ const declarationIndex = (sourceFile) => {
205
228
 
206
229
  export const findDeclaration = (sourceFile, identity) => {
207
230
  const anchor = declarationIndex(sourceFile).get(anchorKey(identity));
208
- if (!anchor || isExportSpecifier(anchor)) return anchor;
231
+ if (!anchor || isExportAnchor(anchor)) return anchor;
209
232
  return declarationNamespaces(anchor).has(identity.namespace) ? anchor : undefined;
210
233
  };
211
234
 
package/src/semantic.mjs CHANGED
@@ -35,6 +35,7 @@ import {
35
35
  declarationsForSymbol,
36
36
  findDeclaration,
37
37
  isDeclaration,
38
+ isExportAnchor,
38
39
  isProjectSource,
39
40
  nodeText,
40
41
  ownerDeclaration,
@@ -735,7 +736,7 @@ const selectSymbolContext = (
735
736
 
736
737
  const resolvedAnchorTarget = (project, anchor, requestedSymbol) => {
737
738
  if (!anchor) return undefined;
738
- if (!isExportSpecifier(anchor)) {
739
+ if (!isExportAnchor(anchor)) {
739
740
  return declarationNamespaces(anchor).has(requestedSymbol.namespace)
740
741
  ? { declaration: anchor, namespace: requestedSymbol.namespace }
741
742
  : undefined;