Skip to content

Commit 0ab90cf

Browse files
committed
Merge branch 'main' into tsgo-port
2 parents c3bd12d + f1a9288 commit 0ab90cf

File tree

11 files changed

+504
-24
lines changed

11 files changed

+504
-24
lines changed

CONTRIBUTING.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
# Note
1+
# Notes on Contributing
22

33
<!-- CODING AGENTS: READ AGENTS.md BEFORE WRITING CODE -->
44

55
🚨 **Important** 🚨: All code changes should be submitted to the https://github.com/microsoft/typescript-go repo. Development in this codebase [is winding down](https://devblogs.microsoft.com/typescript/progress-on-typescript-7-december-2025/#typescript-6.0-is-the-last-javascript-based-release) and PRs will only be merged if they fix **critical** 6.0 issues (at minimum, any bug that existed in 5.9 is not critical unless it's a security issue).
66

7+
## Use of AI Assistance
8+
9+
It is acceptable to use AI tools to assist in developing PRs. However, we ask that you disclose this in the PR description. If your PR appears AI-authored and you do not include this disclosure, your PR will be closed without review. Repeated violation of this will be considered disruptive conduct, which may result in being blocked from interaction with the organization.
10+
711
# Instructions for Logging Issues
812

913
## 1. Read the FAQ

src/jsTyping/jsTyping.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,8 @@ export const enum NameValidationResult {
327327
NameTooLong,
328328
NameStartsWithDot,
329329
NameStartsWithUnderscore,
330-
NameContainsNonURISafeCharacters,
330+
NameContainsInvalidCharacters,
331+
NameContainsNonURISafeCharacters = NameContainsInvalidCharacters, // for backward compatibility
331332
}
332333

333334
const maxPackageNameLength = 214;
@@ -381,8 +382,8 @@ function validatePackageNameWorker(packageName: string, supportScopedPackage: bo
381382
return NameValidationResult.Ok;
382383
}
383384
}
384-
if (encodeURIComponent(packageName) !== packageName) {
385-
return NameValidationResult.NameContainsNonURISafeCharacters;
385+
if (!/^[\w.-]+$/.test(packageName)) {
386+
return NameValidationResult.NameContainsInvalidCharacters;
386387
}
387388
return NameValidationResult.Ok;
388389
}
@@ -405,8 +406,8 @@ function renderPackageNameValidationFailureWorker(typing: string, result: NameVa
405406
return `'${typing}':: ${kind} name '${name}' cannot start with '.'`;
406407
case NameValidationResult.NameStartsWithUnderscore:
407408
return `'${typing}':: ${kind} name '${name}' cannot start with '_'`;
408-
case NameValidationResult.NameContainsNonURISafeCharacters:
409-
return `'${typing}':: ${kind} name '${name}' contains non URI safe characters`;
409+
case NameValidationResult.NameContainsInvalidCharacters:
410+
return `'${typing}':: ${kind} name '${name}' contains invalid characters`;
410411
case NameValidationResult.Ok:
411412
return Debug.fail(); // Shouldn't have called this.
412413
default:

src/lib/es5.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,8 +400,8 @@ interface String {
400400
charAt(pos: number): string;
401401

402402
/**
403-
* Returns the Unicode value of the character at the specified location.
404-
* @param index The zero-based index of the desired character. If there is no character at the specified index, NaN is returned.
403+
* Returns the Unicode value of the character at the specified location, or NaN if the index is out of bounds.
404+
* @param index The zero-based index of the desired character.
405405
*/
406406
charCodeAt(index: number): number;
407407

src/testRunner/unittests/tsserver/codeFix.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,24 @@ describe("unittests:: tsserver:: codeFix::", () => {
5656
});
5757
baselineTsserverLogs("codeFix", "install package when serialized", session);
5858
});
59+
60+
it("install package rejects invalid package names", () => {
61+
const { host, session } = setup();
62+
// A client could craft an applyCodeActionCommand with arbitrary package names.
63+
// The server must validate and reject names with invalid characters to prevent shell injection.
64+
for (const packageName of ["; echo 'hello' #", "react'test", "a/b/c"]) {
65+
session.executeCommandSeq<ts.server.protocol.ApplyCodeActionCommandRequest>({
66+
command: ts.server.protocol.CommandTypes.ApplyCodeActionCommand,
67+
arguments: {
68+
command: {
69+
type: "install package",
70+
file: "/home/src/projects/project/src/file.ts",
71+
packageName,
72+
},
73+
},
74+
});
75+
}
76+
host.runPendingInstalls();
77+
baselineTsserverLogs("codeFix", "install package rejects invalid package names", session);
78+
});
5979
});

src/testRunner/unittests/tsserver/typingsInstaller.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1524,10 +1524,11 @@ describe("unittests:: tsserver:: typingsInstaller:: Validate package name:", ()
15241524
it("package name cannot start with underscore", () => {
15251525
assert.equal(validatePackageName("_foo"), NameValidationResult.NameStartsWithUnderscore);
15261526
});
1527-
it("package non URI safe characters are not supported", () => {
1528-
assert.equal(validatePackageName(" scope "), NameValidationResult.NameContainsNonURISafeCharacters);
1529-
assert.equal(validatePackageName("; say ‘Hello from TypeScript!’ #"), NameValidationResult.NameContainsNonURISafeCharacters);
1530-
assert.equal(validatePackageName("a/b/c"), NameValidationResult.NameContainsNonURISafeCharacters);
1527+
it("package invalid characters are not supported", () => {
1528+
assert.equal(validatePackageName(" scope "), NameValidationResult.NameContainsInvalidCharacters);
1529+
assert.equal(validatePackageName("; say ‘Hello from TypeScript!’ #"), NameValidationResult.NameContainsInvalidCharacters);
1530+
assert.equal(validatePackageName("a/b/c"), NameValidationResult.NameContainsInvalidCharacters);
1531+
assert.equal(validatePackageName("react'test"), NameValidationResult.NameContainsInvalidCharacters);
15311532
});
15321533
it("scoped package name is supported", () => {
15331534
assert.equal(validatePackageName("@scope/bar"), NameValidationResult.Ok);
@@ -1540,20 +1541,20 @@ describe("unittests:: tsserver:: typingsInstaller:: Validate package name:", ()
15401541
assert.deepEqual(validatePackageName("@_scope/bar"), { name: "_scope", isScopeName: true, result: NameValidationResult.NameStartsWithUnderscore });
15411542
assert.deepEqual(validatePackageName("@_scope/_bar"), { name: "_scope", isScopeName: true, result: NameValidationResult.NameStartsWithUnderscore });
15421543
});
1543-
it("scope name in scoped package name with non URI safe characters are not supported", () => {
1544-
assert.deepEqual(validatePackageName("@ scope /bar"), { name: " scope ", isScopeName: true, result: NameValidationResult.NameContainsNonURISafeCharacters });
1545-
assert.deepEqual(validatePackageName("@; say ‘Hello from TypeScript!’ #/bar"), { name: "; say ‘Hello from TypeScript!’ #", isScopeName: true, result: NameValidationResult.NameContainsNonURISafeCharacters });
1546-
assert.deepEqual(validatePackageName("@ scope / bar "), { name: " scope ", isScopeName: true, result: NameValidationResult.NameContainsNonURISafeCharacters });
1544+
it("scope name in scoped package name with invalid characters are not supported", () => {
1545+
assert.deepEqual(validatePackageName("@ scope /bar"), { name: " scope ", isScopeName: true, result: NameValidationResult.NameContainsInvalidCharacters });
1546+
assert.deepEqual(validatePackageName("@; say ‘Hello from TypeScript!’ #/bar"), { name: "; say ‘Hello from TypeScript!’ #", isScopeName: true, result: NameValidationResult.NameContainsInvalidCharacters });
1547+
assert.deepEqual(validatePackageName("@ scope / bar "), { name: " scope ", isScopeName: true, result: NameValidationResult.NameContainsInvalidCharacters });
15471548
});
15481549
it("package name in scoped package name cannot start with dot", () => {
15491550
assert.deepEqual(validatePackageName("@scope/.bar"), { name: ".bar", isScopeName: false, result: NameValidationResult.NameStartsWithDot });
15501551
});
15511552
it("package name in scoped package name cannot start with underscore", () => {
15521553
assert.deepEqual(validatePackageName("@scope/_bar"), { name: "_bar", isScopeName: false, result: NameValidationResult.NameStartsWithUnderscore });
15531554
});
1554-
it("package name in scoped package name with non URI safe characters are not supported", () => {
1555-
assert.deepEqual(validatePackageName("@scope/ bar "), { name: " bar ", isScopeName: false, result: NameValidationResult.NameContainsNonURISafeCharacters });
1556-
assert.deepEqual(validatePackageName("@scope/; say ‘Hello from TypeScript!’ #"), { name: "; say ‘Hello from TypeScript!’ #", isScopeName: false, result: NameValidationResult.NameContainsNonURISafeCharacters });
1555+
it("package name in scoped package name with invalid characters are not supported", () => {
1556+
assert.deepEqual(validatePackageName("@scope/ bar "), { name: " bar ", isScopeName: false, result: NameValidationResult.NameContainsInvalidCharacters });
1557+
assert.deepEqual(validatePackageName("@scope/; say ‘Hello from TypeScript!’ #"), { name: "; say ‘Hello from TypeScript!’ #", isScopeName: false, result: NameValidationResult.NameContainsInvalidCharacters });
15571558
});
15581559
});
15591560

src/typingsInstallerCore/typingsInstaller.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,22 @@ export abstract class TypingsInstaller {
239239
/** @internal */
240240
installPackage(req: InstallPackageRequest): void {
241241
const { fileName, packageName, projectName, projectRootPath, id } = req;
242+
const validationResult = JsTyping.validatePackageName(packageName);
243+
if (validationResult !== JsTyping.NameValidationResult.Ok) {
244+
const message = JsTyping.renderPackageNameValidationFailure(validationResult, packageName);
245+
if (this.log.isEnabled()) {
246+
this.log.writeLine(message);
247+
}
248+
const response: PackageInstalledResponse = {
249+
kind: ActionPackageInstalled,
250+
projectName,
251+
id,
252+
success: false,
253+
message,
254+
};
255+
this.sendResponse(response);
256+
return;
257+
}
242258
const cwd = forEachAncestorDirectory(getDirectoryPath(fileName), directory => {
243259
if (this.installTypingHost.fileExists(combinePaths(directory, "package.json"))) {
244260
return directory;

tests/baselines/reference/completionsStringMethods.baseline

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@
209209
],
210210
"documentation": [
211211
{
212-
"text": "Returns the Unicode value of the character at the specified location.",
212+
"text": "Returns the Unicode value of the character at the specified location, or NaN if the index is out of bounds.",
213213
"kind": "text"
214214
}
215215
],
@@ -226,7 +226,7 @@
226226
"kind": "space"
227227
},
228228
{
229-
"text": "The zero-based index of the desired character. If there is no character at the specified index, NaN is returned.",
229+
"text": "The zero-based index of the desired character.",
230230
"kind": "text"
231231
}
232232
]

0 commit comments

Comments
 (0)