Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
129 commits
Select commit Hold shift + click to select a range
5bc9c0f
Add typed path invariants to tsc
jakebailey Aug 29, 2026
1d1806c
Enforce typed path conversions
jakebailey Aug 30, 2026
206daa5
Clarify typed path identities
jakebailey Aug 30, 2026
293c380
Retain typed file names through compiler APIs
jakebailey Aug 30, 2026
f7d423f
Expose typed paths through JavaScript APIs
jakebailey Aug 30, 2026
7587f7e
Confine JavaScript path assertions
jakebailey Aug 30, 2026
13bf0f4
Document and enforce the typed path lattice
jakebailey Aug 31, 2026
fb9db64
Retain typed config file names
jakebailey Aug 31, 2026
b7300ee
Retain typed paths through module resolution
jakebailey Aug 31, 2026
1ee4f2c
Retain typed compiler directories
jakebailey Aug 31, 2026
af17862
Retain typed package directories
jakebailey Aug 31, 2026
d7232ad
Type package json lookups
jakebailey Aug 31, 2026
75bb9db
Retain typed installed typings
jakebailey Aug 31, 2026
ced2724
Retain typed extended config paths
jakebailey Aug 31, 2026
04ea6e4
Retain typed source map paths
jakebailey Aug 31, 2026
255db66
Require typed path bases
jakebailey Aug 31, 2026
590b013
Retain typed declaration output paths
jakebailey Aug 31, 2026
fb6aaad
Retain typed root file names
jakebailey Aug 31, 2026
bb2d697
Push path invariants to boundaries
jakebailey Aug 31, 2026
39fd599
Retain typed path policies
jakebailey Aug 31, 2026
6d46453
Push path proofs through compiler outputs
jakebailey Aug 31, 2026
4c38c53
Retain normalized root and config paths
jakebailey Aug 31, 2026
f89569f
Make path resolution intent explicit
jakebailey Aug 31, 2026
1978617
Retain typed paths through file sources
jakebailey Aug 31, 2026
cb21bf2
Type the VFS path contract
jakebailey Aug 31, 2026
72c2ad8
Name lossless path conversions explicitly
jakebailey Aug 31, 2026
72785eb
Name transforming path conversion explicitly
jakebailey Aug 31, 2026
1dd91f7
Retain typed paths through language service hosts
jakebailey Aug 31, 2026
2363033
Retain typed paths through watch planning
jakebailey Aug 31, 2026
f6f3fe7
Retain typed paths through internal producers
jakebailey Aug 31, 2026
9c072c2
Retain typed paths through host callbacks
jakebailey Aug 31, 2026
854a8d5
Retain typed paths through snapshots and completions
jakebailey Aug 31, 2026
0658af4
Separate path rooting from case sensitivity
jakebailey Sep 1, 2026
2cedc35
Use a real case sensitivity enum
jakebailey Sep 1, 2026
9843fe3
Remove redundant path context parameters
jakebailey Sep 1, 2026
310f0dc
Remove redundant path normalization
jakebailey Sep 1, 2026
e4f7af7
Collapse duplicate filename representations
jakebailey Sep 1, 2026
9584c6f
Strengthen filename boundaries
jakebailey Sep 1, 2026
3aaf6ad
Type normalized relative paths
jakebailey Sep 1, 2026
14f6c5e
Use typed path comparisons
jakebailey Sep 1, 2026
158050e
Preserve types through path transforms
jakebailey Sep 1, 2026
97a498e
Use typed path inspection
jakebailey Sep 1, 2026
d34c208
Type source map roots
jakebailey Sep 1, 2026
9c83af0
Type file rename path updates
jakebailey Sep 1, 2026
b53413e
Type module resolution candidates
jakebailey Sep 1, 2026
b4ec4e2
Classify command line option paths
jakebailey Sep 1, 2026
2740cf9
Type finalized compiler path options
jakebailey Sep 1, 2026
c2e9275
Separate compiler option wire values
jakebailey Sep 1, 2026
1369ff5
Type core filename helpers
jakebailey Sep 1, 2026
cc7a622
Type program file lookups
jakebailey Sep 1, 2026
3d1ccd6
Type redirect and symlink metadata
jakebailey Sep 1, 2026
6f2fdfa
Type module specifier contracts
jakebailey Sep 1, 2026
b306b26
Type config file specifications
jakebailey Sep 1, 2026
e530ddc
Type file watch adapters
jakebailey Sep 1, 2026
81dc011
Type content mapper paths
jakebailey Sep 1, 2026
f23e336
Type API path contracts
jakebailey Sep 1, 2026
b6e61a1
Type LSP path contracts
jakebailey Sep 1, 2026
bca12f2
Type TypeScript API paths
jakebailey Sep 1, 2026
ab46b45
Type build info path boundaries
jakebailey Sep 1, 2026
bd2d9bf
Type vfsmatch path boundaries
jakebailey Sep 1, 2026
3e1b73a
Close remaining typed path boundaries
jakebailey Sep 1, 2026
cd636f5
Reduce working directory path plumbing
jakebailey Sep 1, 2026
e517a1f
Preserve typed paths through remaining internals
jakebailey Sep 2, 2026
46f2a55
Keep PathKey as a canonical sink
jakebailey Sep 2, 2026
b629d65
Preserve canonical build info serialization
jakebailey Sep 2, 2026
011067f
Fix inherited path presentation loss (PORT TO MAIN)
jakebailey Sep 2, 2026
b4a03c6
Type serialized build info paths
jakebailey Sep 2, 2026
7f93128
Fix module specifier path checks
jakebailey Sep 2, 2026
2765813
Preserve types through path matching
jakebailey Sep 2, 2026
494a226
Preserve types through remaining path checks
jakebailey Sep 2, 2026
9343446
Test nested triple-slash redirects (PORT TO MAIN)
jakebailey Sep 2, 2026
9f82663
Fix nested triple-slash redirects (PORT TO MAIN)
jakebailey Sep 2, 2026
79f1d67
Avoid typed path string round trips
jakebailey Sep 2, 2026
695d885
Test auto-import package directory realpaths (PORT TO MAIN)
jakebailey Sep 2, 2026
ec65144
Fix auto-import package directory realpaths (PORT TO MAIN)
jakebailey Sep 2, 2026
4f3f8e6
Test node_modules package path kinds (PORT TO MAIN)
jakebailey Sep 2, 2026
11bb359
Type node_modules package path kinds (PORT TO MAIN)
jakebailey Sep 2, 2026
46ef05f
Preserve file names through VFS writes
jakebailey Sep 2, 2026
6eb727c
Preserve directory types at LSP boundaries
jakebailey Sep 2, 2026
20dd2c6
Type file rename endpoints by path kind
jakebailey Sep 2, 2026
ed28db2
Replace node_modules path offsets with semantic parts
jakebailey Sep 2, 2026
c8e94ed
Preserve types through module specifier joins
jakebailey Sep 2, 2026
fbad08b
Test nested package.json specifiers (PORT TO MAIN)
jakebailey Sep 2, 2026
3938725
Fix nested package.json specifiers (PORT TO MAIN)
jakebailey Sep 2, 2026
921fddb
Preserve module specifiers through source lookup
jakebailey Sep 2, 2026
a29bf49
Remove project reference path kind booleans
jakebailey Sep 2, 2026
a5a668b
Modernize node_modules search root selection
jakebailey Sep 2, 2026
2be0f2a
Pass path case sensitivity directly
jakebailey Sep 2, 2026
511226c
Derive path sensitivity from filesystem
jakebailey Sep 2, 2026
ec3c18d
Derive compiler host directory from project
jakebailey Sep 2, 2026
85f6d57
Test project-relative API import edits (PORT TO MAIN)
jakebailey Sep 2, 2026
799798e
Fix project-relative API import edits (PORT TO MAIN)
jakebailey Sep 2, 2026
91b0876
Test project-relative API source cache (PORT TO MAIN)
jakebailey Sep 2, 2026
231fa47
Fix project-relative API source cache (PORT TO MAIN)
jakebailey Sep 2, 2026
ff5900b
Keep API path rooting at owning boundary
jakebailey Sep 2, 2026
53ef09e
Limit file loader current directory fallback
jakebailey Sep 2, 2026
20f8a8f
Lift current directory out of compiler hosts
jakebailey Sep 2, 2026
4f20ec9
Merge branch 'main' into typed-paths
jakebailey Sep 2, 2026
167628b
Avoid renormalizing typed paths
jakebailey Sep 2, 2026
d49f2fe
Avoid duplicate typed path work
jakebailey Sep 2, 2026
68ac17e
Key package JSON cache by directory
jakebailey Sep 2, 2026
69dd250
Carry path identity through resolution
jakebailey Sep 2, 2026
7c3edca
Tighten typed path boundaries
jakebailey Sep 2, 2026
3da9071
Rename rooted path types
jakebailey Sep 3, 2026
ba6ec0f
Keep semantic directories as paths
jakebailey Sep 3, 2026
bacb121
Remove path prefix types
jakebailey Sep 3, 2026
80303a2
Move canonicalization onto case sensitivity
jakebailey Sep 3, 2026
df6089b
Clarify PathKey producer names
jakebailey Sep 3, 2026
27a42a6
Reuse resolved module path keys
jakebailey Sep 3, 2026
15e280c
Fix public TypeScript path API
jakebailey Sep 3, 2026
bf7b228
Remove fused TypeScript path conversion
jakebailey Sep 3, 2026
047aedd
Tighten TypeScript path usage
jakebailey Sep 3, 2026
23c3630
Match Go Unicode path casing
jakebailey Sep 3, 2026
0ed31a7
Clarify typed path documentation
jakebailey Sep 3, 2026
bd42e3d
Describe PathKey as a canonical key
jakebailey Sep 3, 2026
711b128
Use direct case sensitivity terminology
jakebailey Sep 3, 2026
b3b50b0
Remove fused path key conversion
jakebailey Sep 3, 2026
5deb490
Tighten absolute path construction
jakebailey Sep 3, 2026
1ca9a92
Tighten TypeScript path API boundaries
jakebailey Sep 3, 2026
c9200e5
Validate typed path constructor constants
jakebailey Sep 3, 2026
4955ce4
Reject malformed path root boundaries
jakebailey Sep 3, 2026
dcc8556
Fix rooted path LSP generation
jakebailey Sep 4, 2026
5f3e1b5
Validate typed path literals in tests
jakebailey Sep 4, 2026
17c472b
Cover typed path map key literals
jakebailey Sep 4, 2026
14a5ebf
Test typed path zero value literals
jakebailey Sep 4, 2026
e1b88d5
Handle cross-volume custom lint tests
jakebailey Sep 4, 2026
3a0b159
Fix typed path review regressions
jakebailey Sep 4, 2026
c5b7672
Preserve rooted augmentation specifiers
jakebailey Sep 4, 2026
e4c4ac8
Merge branch 'main' into typed-paths
jakebailey Sep 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions Herebyfile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ const enumDefs = [
{ name: "NewLineKind", goPrefix: "NewLineKind", goFile: "tsc/internal/core/compileroptions.go", outDir: "packages/typescript/src/enums" },
{ name: "JsxEmit", goPrefix: "JsxEmit", goFile: "tsc/internal/core/compileroptions.go", outDir: "packages/typescript/src/enums" },
{ name: "ScriptKind", goPrefix: "ScriptKind", goFile: "tsc/internal/core/scriptkind.go", outDir: "packages/typescript/src/enums" },
{ name: "CaseSensitivity", goPrefix: "Case", goFile: "tsc/internal/tspath/path.go", outDir: "packages/typescript/src/enums" },
{ name: "TokenFlags", goPrefix: "TokenFlags", goFile: "tsc/internal/ast/tokenflags.go", outDir: "packages/typescript/src/enums" },
{ name: "DiagnosticDirectivePolicy", goPrefix: "MappedDiagnosticDirectivePolicy", goFile: "tsc/internal/ast/ast.go", outDir: "packages/typescript/src/enums" },
{ name: "SpanMapKind", goPrefix: "Kind", goFile: "tsc/internal/spanmap/spanmap.go", outDir: "packages/typescript/src/enums" },
Expand Down
4 changes: 4 additions & 0 deletions packages/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@
"@typescript/source": "./src/api/fs.ts",
"default": "./dist/api/fs.js"
},
"./unstable/path": {
"@typescript/source": "./src/api/typedPaths.ts",
"default": "./dist/api/typedPaths.js"
},
"./unstable/proto": {
"@typescript/source": "./src/api/proto.ts",
"default": "./dist/api/proto.js"
Expand Down
151 changes: 94 additions & 57 deletions packages/typescript/src/api/async/api.ts

Large diffs are not rendered by default.

68 changes: 43 additions & 25 deletions packages/typescript/src/api/async/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import {
} from "#vscode-jsonrpc/node";
import type { ChildProcess } from "node:child_process";
import type { Socket } from "node:net";
import type {
RootedDirectoryPath,
RootedFilePath,
RootedPath,
} from "../../ast/index.ts";
import {
type FileSystem,
fsCallbackNames,
Expand Down Expand Up @@ -141,32 +146,45 @@ export class Client {
private registerFSCallbacks(connection: MessageConnection, fs: FileSystem | undefined): void {
if (!fs) return;
for (const name of fsCallbackNames) {
if (name === "writeFile") {
if (!fs.writeFile) continue;
const callback = fs.writeFile;

const requestType = new RequestType<{ path: string; data: string; }, unknown, void>(name);
connection.onRequest(requestType, (arg: { path: string; data: string; }) => {
callback(arg.path, arg.data);
return null;
});

continue;
}

const callback = fs[name];
if (callback) {
const requestType = new RequestType<unknown, unknown, void>(name);
connection.onRequest(requestType, (arg: unknown) => {
const result = callback(arg as any);
if (name === "readFile") {
// readFile has 3 returns: string (content), null (not found), undefined (fall back).
// JSON-RPC can't distinguish null from undefined, so wrap in object.
if (result === undefined) return null;
return { content: result };
switch (name) {
case "readFile":
if (fs.readFile) {
connection.onRequest(new RequestType<RootedFilePath, unknown, void>(name), fileName => {
const result = fs.readFile!(fileName);
// readFile has 3 returns: string (content), null (not found), undefined (fall back).
// JSON-RPC can't distinguish null from undefined, so wrap in object.
return result === undefined ? null : { content: result };
});
}
break;
case "fileExists":
if (fs.fileExists) {
connection.onRequest(new RequestType<RootedFilePath, unknown, void>(name), fileName => fs.fileExists!(fileName) ?? null);
}
break;
case "directoryExists":
if (fs.directoryExists) {
connection.onRequest(new RequestType<RootedDirectoryPath, unknown, void>(name), directoryName => fs.directoryExists!(directoryName) ?? null);
}
break;
case "getAccessibleEntries":
if (fs.getAccessibleEntries) {
connection.onRequest(new RequestType<RootedDirectoryPath, unknown, void>(name), directoryName => fs.getAccessibleEntries!(directoryName) ?? null);
}
break;
case "realpath":
if (fs.realpath) {
connection.onRequest(new RequestType<RootedPath, unknown, void>(name), path => fs.realpath!(path) ?? null);
}
break;
case "writeFile":
if (fs.writeFile) {
connection.onRequest(new RequestType<{ path: RootedFilePath; data: string; }, unknown, void>(name), arg => {
fs.writeFile!(arg.path, arg.data);
return null;
});
}
return result ?? null;
});
break;
}
}
}
Expand Down
12 changes: 8 additions & 4 deletions packages/typescript/src/api/async/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import type {
NamedTupleMember,
ParameterDeclaration,
} from "../../ast/ast.ts";
import type {
RootedDirectoryPath,
RootedFilePath,
} from "../../ast/index.ts";
import type { Diagnostic } from "../proto.ts";
import type {
NodeHandle,
Expand Down Expand Up @@ -387,26 +391,26 @@ export interface CompletionInfo {
}

export interface FormatDiagnosticsHost {
getCurrentDirectory(): string;
getCurrentDirectory(): RootedDirectoryPath;
getCanonicalFileName(fileName: string): string;
getNewLine(): string;
}

export interface EmitOutputFile {
readonly text: string;
readonly sourceFileName?: string | undefined;
readonly sourceFileName?: RootedFilePath | undefined;
}

export interface EmitResult {
readonly emitSkipped: boolean;
readonly diagnostics: readonly Diagnostic[];
readonly emittedFiles: readonly string[];
readonly emittedFiles: readonly RootedFilePath[];
}

export interface EmitOutput {
readonly emitSkipped: boolean;
readonly diagnostics: readonly Diagnostic[];
readonly outputFiles: ReadonlyMap<string, EmitOutputFile>;
readonly outputFiles: ReadonlyMap<RootedFilePath, EmitOutputFile>;
}

export interface ImportSymbolAction {
Expand Down
8 changes: 6 additions & 2 deletions packages/typescript/src/api/diagnosticFormatter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import type {
RootedDirectoryPath,
RootedFilePath,
} from "../ast/index.ts";
import { convertToRelativePath } from "./path.ts";
import type { DiagnosticResponse as Diagnostic } from "./proto.generated.ts";

export interface FormatDiagnosticsHost {
getCurrentDirectory(): string;
getCurrentDirectory(): RootedDirectoryPath;
getCanonicalFileName(fileName: string): string;
getNewLine(): string;
}
Expand Down Expand Up @@ -70,7 +74,7 @@ function flattenDiagnosticMessage(diagnostic: Diagnostic, newLine: string, inden
return result;
}

function relativeFileName(fileName: string, host: FormatDiagnosticsHost): string {
function relativeFileName(fileName: RootedFilePath, host: FormatDiagnosticsHost): string {
return convertToRelativePath(
fileName,
host.getCurrentDirectory(),
Expand Down
77 changes: 48 additions & 29 deletions packages/typescript/src/api/fs.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,42 @@
import { getPathComponents } from "./path.ts";
import type {
RootedDirectoryPath,
RootedFilePath,
RootedPath,
} from "../ast/index.ts";
import {
getPathComponents,
toRootedFilePath,
} from "./path.ts";

export interface FileSystemEntries {
files: string[];
directories: string[];
}

export interface FileSystem {
directoryExists?: (directoryName: string) => boolean | undefined;
fileExists?: (fileName: string) => boolean | undefined;
getAccessibleEntries?: (directoryName: string) => FileSystemEntries | undefined;
directoryExists?: (directoryName: RootedDirectoryPath) => boolean | undefined;
fileExists?: (fileName: RootedFilePath) => boolean | undefined;
getAccessibleEntries?: (directoryName: RootedDirectoryPath) => FileSystemEntries | undefined;
/**
* Read a file's content.
* - Return the file content as a `string` (including `""` for empty files).
* - Return `null` to indicate the file does not exist (without falling back to the real FS).
* - Return `undefined` to fall back to the real filesystem.
*/
readFile?: (fileName: string) => string | null | undefined;
realpath?: (path: string) => string | undefined;
writeFile?: (path: string, content: string) => void;
removeFile?: (path: string) => void;
readFile?: (fileName: RootedFilePath) => string | null | undefined;
realpath?: (path: RootedPath) => RootedPath | undefined;
writeFile?: (path: RootedFilePath, content: string) => void;
removeFile?: (path: RootedFilePath) => void;
}

export interface VirtualFileSystem extends FileSystem {
directoryExists(directoryName: RootedDirectoryPath): boolean;
fileExists(fileName: RootedFilePath): boolean;
getAccessibleEntries(directoryName: RootedDirectoryPath): FileSystemEntries | undefined;
readFile(fileName: RootedFilePath): string | undefined;
realpath(path: RootedPath): RootedPath;
writeFile(path: RootedFilePath, content: string): void;
removeFile(path: RootedFilePath): void;
}

/** The callback names supported by the Go server for virtual FS delegation. */
Expand All @@ -35,15 +53,16 @@ interface VFile {

type VNode = VDirectory | VFile;

export function createVirtualFileSystem(files: Record<string, string>): FileSystem {
export function createVirtualFileSystem(files: Record<string, string>): VirtualFileSystem {
const root: VDirectory = {
type: "directory",
children: {},
};
const content: Record<string, string> = {};
const content = new Map<RootedFilePath, string>();

for (const filePath of Object.keys(files)) {
content[filePath] = files[filePath];
for (const [rawFilePath, data] of Object.entries(files)) {
const filePath = toRootedFilePath(rawFilePath, undefined);
content.set(filePath, data);
addToTree(filePath);
}

Expand All @@ -57,11 +76,14 @@ export function createVirtualFileSystem(files: Record<string, string>): FileSyst
removeFile,
};

function getNodeFromPath(path: string): VNode | undefined {
function getNodeFromPath(path: RootedPath): VNode | undefined {
if (!path || path === "/") {
return root;
}
const segments = getPathComponents(path).slice(1);
return getNodeFromSegments(getPathComponents(path).slice(1));
}

function getNodeFromSegments(segments: readonly string[]): VNode | undefined {
let current: VNode = root;
for (const segment of segments) {
if (current.type !== "directory") {
Expand Down Expand Up @@ -90,7 +112,7 @@ export function createVirtualFileSystem(files: Record<string, string>): FileSyst
return current;
}

function addToTree(path: string): void {
function addToTree(path: RootedFilePath): void {
const segments = getPathComponents(path).slice(1);
if (segments.length === 0) {
throw new Error(`Invalid file path: "${path}"`);
Expand All @@ -100,32 +122,32 @@ export function createVirtualFileSystem(files: Record<string, string>): FileSyst
dirNode.children[filename] = { type: "file" };
}

function writeFile(path: string, data: string): void {
content[path] = data;
function writeFile(path: RootedFilePath, data: string): void {
content.set(path, data);
addToTree(path);
}

function removeFile(path: string): void {
delete content[path];
function removeFile(path: RootedFilePath): void {
content.delete(path);
const segments = getPathComponents(path).slice(1);
if (segments.length === 0) return;
const filename = segments.pop()!;
const dirNode = getNodeFromPath("/" + segments.join("/"));
const dirNode = getNodeFromSegments(segments);
if (dirNode && dirNode.type === "directory") {
delete dirNode.children[filename];
}
}

function directoryExists(directoryName: string): boolean {
function directoryExists(directoryName: RootedDirectoryPath): boolean {
const node = getNodeFromPath(directoryName);
return !!node && node.type === "directory";
}

function fileExists(fileName: string): boolean {
return fileName in content;
function fileExists(fileName: RootedFilePath): boolean {
return content.has(fileName);
}

function getAccessibleEntries(directoryName: string): FileSystemEntries | undefined {
function getAccessibleEntries(directoryName: RootedDirectoryPath): FileSystemEntries | undefined {
const node = getNodeFromPath(directoryName);
if (!node || node.type !== "directory") {
return undefined;
Expand All @@ -143,10 +165,7 @@ export function createVirtualFileSystem(files: Record<string, string>): FileSyst
return { files: fileEntries, directories };
}

function readFile(fileName: string): string | undefined {
if (fileName in content) {
return content[fileName];
}
return undefined;
function readFile(fileName: RootedFilePath): string | undefined {
return content.get(fileName);
}
}
3 changes: 2 additions & 1 deletion packages/typescript/src/api/node/node.infrastructure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
type FileReference,
ModifierFlags,
type Node,
type PathKey,
SyntaxKind,
} from "../../ast/index.ts";
import type { TimingCollector } from "../timing.ts";
Expand Down Expand Up @@ -52,7 +53,7 @@ export interface SourceFileInfo {
readonly _offsetStructuredData: number;
readonly _decoder: TextDecoder;
nodes: any[];
readonly path?: string;
readonly path?: PathKey;
/**
* The timing collector that per-node materialization is reported into, and
* that this source file registered itself with when fetched. Present only
Expand Down
Loading
Loading