-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Avoid allocation for normalized absolute paths. #60755
Changes from all commits
45070c4
209073e
baaa850
3a27437
a117f5a
f7da1e6
4ff74c9
4db32f6
eade65a
cc3722c
28b7cdf
2886cc7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,6 +62,7 @@ import { | |
isNumber, | ||
isString, | ||
map, | ||
mapDefined, | ||
mapDefinedIterator, | ||
maybeBind, | ||
noop, | ||
|
@@ -1236,7 +1237,7 @@ function getBuildInfo(state: BuilderProgramStateWithDefinedProgram): BuildInfo { | |
const currentDirectory = state.program.getCurrentDirectory(); | ||
const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(state.compilerOptions)!, currentDirectory)); | ||
// Convert the file name to Path here if we set the fileName instead to optimize multiple d.ts file emits and having to compute Canonical path | ||
const latestChangedDtsFile = state.latestChangedDtsFile ? relativeToBuildInfoEnsuringAbsolutePath(state.latestChangedDtsFile) : undefined; | ||
const latestChangedDtsFile = state.latestChangedDtsFile ? relativePathToBuildInfo(state.latestChangedDtsFile) : undefined; | ||
const fileNames: string[] = []; | ||
const fileNameToFileId = new Map<string, IncrementalBuildInfoFileId>(); | ||
const rootFileNames = new Set(state.program.getRootFileNames().map(f => toPath(f, currentDirectory, state.program.getCanonicalFileName))); | ||
|
@@ -1377,10 +1378,17 @@ function getBuildInfo(state: BuilderProgramStateWithDefinedProgram): BuildInfo { | |
}; | ||
return buildInfo; | ||
|
||
function relativeToBuildInfoEnsuringAbsolutePath(path: string) { | ||
function relativePathToBuildInfo(path: string) { | ||
return relativeToBuildInfo(getNormalizedAbsolutePath(path, currentDirectory)); | ||
} | ||
|
||
function relativePathToBuildInfoOrOriginalValue(path: unknown): string | undefined { | ||
if (typeof path === "string") { | ||
return relativePathToBuildInfo(path); | ||
} | ||
return undefined; | ||
} | ||
|
||
function relativeToBuildInfo(path: string) { | ||
return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory, path, state.program.getCanonicalFileName)); | ||
} | ||
|
@@ -1457,13 +1465,13 @@ function getBuildInfo(state: BuilderProgramStateWithDefinedProgram): BuildInfo { | |
if (option) { | ||
Debug.assert(option.type !== "listOrElement"); | ||
if (option.type === "list") { | ||
const values = value as readonly string[]; | ||
const values = value as readonly unknown[]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I actually think that this type assertion probably isn't correct, and we need to validate that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I couldn't get this to fail by writing a test. |
||
if (option.element.isFilePath && values.length) { | ||
return values.map(relativeToBuildInfoEnsuringAbsolutePath); | ||
return mapDefined(values, relativePathToBuildInfoOrOriginalValue); | ||
} | ||
} | ||
else if (option.isFilePath) { | ||
return relativeToBuildInfoEnsuringAbsolutePath(value as string); | ||
return relativePathToBuildInfoOrOriginalValue(value); | ||
} | ||
} | ||
return value; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -594,7 +594,9 @@ function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOpt | |
return pathsOnly ? undefined : relativePath; | ||
} | ||
|
||
const baseDirectory = getNormalizedAbsolutePath(getPathsBasePath(compilerOptions, host) || baseUrl!, host.getCurrentDirectory()); | ||
const currentDirectory = host.getCurrentDirectory(); | ||
const basePath = getPathsBasePath(compilerOptions, host) ?? baseUrl ?? currentDirectory; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We incorrectly assumed |
||
const baseDirectory = getNormalizedAbsolutePath(basePath, currentDirectory); | ||
const relativeToBaseUrl = getRelativePathIfInSameVolume(moduleFileName, baseDirectory, getCanonicalFileName); | ||
if (!relativeToBaseUrl) { | ||
return pathsOnly ? undefined : relativePath; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -625,7 +625,28 @@ export function getNormalizedPathComponents(path: string, currentDirectory: stri | |
|
||
/** @internal */ | ||
export function getNormalizedAbsolutePath(fileName: string, currentDirectory: string | undefined): string { | ||
return getPathFromPathComponents(getNormalizedPathComponents(fileName, currentDirectory)); | ||
fileName = normalizeSlashes(fileName); | ||
|
||
if (isNotNormalizedOrAbsolute(fileName)) { | ||
return getPathFromPathComponents(getNormalizedPathComponents(fileName, currentDirectory)); | ||
Comment on lines
+630
to
+631
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I also wonder about both There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I absolutely agree on that part, I think that iterating through each non-normalized component would be way better. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
|
||
return fileName; | ||
} | ||
|
||
function isNotNormalizedOrAbsolute(s: string) { | ||
if (getEncodedRootLength(s) === 0) { | ||
// An absolute path must have a root component. | ||
return true; | ||
} | ||
|
||
const lastChar = s.charCodeAt(s.length - 1); | ||
if (lastChar === CharacterCodes.slash) { | ||
// A normalized path cannot end in a trailing separator. | ||
return true; | ||
} | ||
|
||
return relativePathSegmentRegExp.test(s); | ||
} | ||
|
||
/** @internal */ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -269,6 +269,110 @@ describe("unittests:: core paths", () => { | |
assert.strictEqual(ts.resolvePath("a", "b", "/c"), "/c"); | ||
assert.strictEqual(ts.resolvePath("a", "b", "../c"), "a/c"); | ||
}); | ||
it("getNormalizedAbsolutePath", () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am surprised #60802 hasn't made this diff go away, maybe needs a main merge? |
||
assert.strictEqual(ts.getNormalizedAbsolutePath("/", ""), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/.", ""), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/./", ""), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/../", ""), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/a", ""), "/a"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/a/", ""), "/a"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/a/.", ""), "/a"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/a/foo.", ""), "/a/foo."); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/a/./", ""), "/a"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/a/./b", ""), "/a/b"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/a/./b/", ""), "/a/b"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/a/..", ""), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/a/../", ""), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/a/../", ""), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/a/../b", ""), "/b"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/a/../b/", ""), "/b"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/a/..", ""), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/a/..", "/"), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/a/..", "b/"), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/a/..", "/b"), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/a/.", "b"), "/a"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/a/.", "."), "/a"); | ||
|
||
// Tests as above, but with backslashes. | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\", ""), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\.", ""), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\.\\", ""), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\..\\", ""), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\a\\.\\", ""), "/a"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\a\\.\\b", ""), "/a/b"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\a\\.\\b\\", ""), "/a/b"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\a\\..", ""), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\a\\..\\", ""), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\a\\..\\", ""), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\a\\..\\b", ""), "/b"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\a\\..\\b\\", ""), "/b"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\a\\..", ""), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\a\\..", "\\"), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\a\\..", "b\\"), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\a\\..", "\\b"), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\a\\.", "b"), "/a"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\a\\.", "."), "/a"); | ||
|
||
// Relative paths on an empty currentDirectory. | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("", ""), ""); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath(".", ""), ""); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("./", ""), ""); | ||
// Strangely, these do not normalize to the empty string. | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("..", ""), ".."); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("../", ""), ".."); | ||
|
||
// Interaction between relative paths and currentDirectory. | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("", "/home"), "/home"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath(".", "/home"), "/home"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("./", "/home"), "/home"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("..", "/home"), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("../", "/home"), "/"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("a", "b"), "b/a"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("a", "b/c"), "b/c/a"); | ||
|
||
// Base names starting or ending with a dot do not affect normalization. | ||
assert.strictEqual(ts.getNormalizedAbsolutePath(".a", ""), ".a"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("..a", ""), "..a"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("a.", ""), "a."); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("a..", ""), "a.."); | ||
|
||
assert.strictEqual(ts.getNormalizedAbsolutePath("/base/./.a", ""), "/base/.a"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/base/../.a", ""), "/.a"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/base/./..a", ""), "/base/..a"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/base/../..a", ""), "/..a"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/base/./..a/b", ""), "/base/..a/b"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/base/../..a/b", ""), "/..a/b"); | ||
|
||
assert.strictEqual(ts.getNormalizedAbsolutePath("/base/./a.", ""), "/base/a."); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/base/../a.", ""), "/a."); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/base/./a..", ""), "/base/a.."); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/base/../a..", ""), "/a.."); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/base/./a../b", ""), "/base/a../b"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/base/../a../b", ""), "/a../b"); | ||
|
||
// Consecutive intermediate slashes are normalized to a single slash. | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("a//b", ""), "a/b"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("a///b", ""), "a/b"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("a/b//c", ""), "a/b/c"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("/a/b//c", ""), "/a/b/c"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("//a/b//c", ""), "//a/b/c"); | ||
|
||
// Backslashes are converted to slashes, | ||
// and then consecutive intermediate slashes are normalized to a single slash | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("a\\\\b", ""), "a/b"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("a\\\\\\b", ""), "a/b"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("a\\b\\\\c", ""), "a/b/c"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\a\\b\\\\c", ""), "/a/b/c"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\\\a\\b\\\\c", ""), "//a/b/c"); | ||
|
||
// The same occurs for mixed slashes. | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("a/\\b", ""), "a/b"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("a\\/b", ""), "a/b"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("a\\/\\b", ""), "a/b"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("a\\b//c", ""), "a/b/c"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\a\\b\\\\c", ""), "/a/b/c"); | ||
assert.strictEqual(ts.getNormalizedAbsolutePath("\\\\a\\b\\\\c", ""), "//a/b/c"); | ||
}); | ||
it("getPathRelativeTo", () => { | ||
assert.strictEqual(ts.getRelativePathFromDirectory("/", "/", /*ignoreCase*/ false), ""); | ||
assert.strictEqual(ts.getRelativePathFromDirectory("/a", "/a", /*ignoreCase*/ false), ""); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -144,7 +144,7 @@ export = x; | |
|
||
|
||
//// [/home/src/workspaces/solution/common/tsconfig.tsbuildinfo] | ||
{"fileNames":["../../../tslibs/ts/lib/lib.d.ts","./obj.json","./index.ts"],"fileIdsList":[[2]],"fileInfos":[{"version":"-32082413277-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ndeclare const console: { log(msg: any): void; };\ninterface SymbolConstructor {\n readonly species: symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\n","affectsGlobalScope":true},"2353615672-{\n \"val\": 42\n}","-5032674136-import x = require(\"./obj.json\");\nexport = x;\n"],"root":[2,3],"options":{"allowJs":true,"checkJs":true,"composite":true,"declaration":true,"esModuleInterop":true,"outDir":"..","rootDir":"..","skipLibCheck":true},"referencedMap":[[3,1]],"latestChangedDtsFile":"./index.d.ts","version":"FakeTSVersion"} | ||
{"fileNames":["../../../tslibs/ts/lib/lib.d.ts","./obj.json","./index.ts"],"fileIdsList":[[2]],"fileInfos":[{"version":"-32082413277-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ndeclare const console: { log(msg: any): void; };\ninterface SymbolConstructor {\n readonly species: symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\n","affectsGlobalScope":true},"2353615672-{\n \"val\": 42\n}","-5032674136-import x = require(\"./obj.json\");\nexport = x;\n"],"root":[2,3],"options":{"allowJs":true,"checkJs":true,"composite":true,"declaration":true,"esModuleInterop":true,"rootDir":"..","skipLibCheck":true},"referencedMap":[[3,1]],"latestChangedDtsFile":"./index.d.ts","version":"FakeTSVersion"} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you look closely, the real diff is something like - "outDir":"..","rootDir":".."
+ "rootDir":".." This is actually correct. Previously, we would try to normalize the path, possibly resolving against the current working directory as a base. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The readable format is below in the diff, FWIW |
||
|
||
//// [/home/src/workspaces/solution/common/tsconfig.tsbuildinfo.readable.baseline.txt] | ||
{ | ||
|
@@ -193,7 +193,6 @@ export = x; | |
"composite": true, | ||
"declaration": true, | ||
"esModuleInterop": true, | ||
"outDir": "..", | ||
"rootDir": "..", | ||
"skipLibCheck": true | ||
}, | ||
|
@@ -204,7 +203,7 @@ export = x; | |
}, | ||
"latestChangedDtsFile": "./index.d.ts", | ||
"version": "FakeTSVersion", | ||
"size": 1148 | ||
"size": 1134 | ||
} | ||
|
||
//// [/home/src/workspaces/out/sub-project/index.js] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not entirely sure what to do here. Our options parser does discard invalid values. I wanted to make the most minimal change to
builder.ts
on this one.