diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 759eaa1f5d2..d379885a4cb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -39786,6 +39786,16 @@ namespace ts { return false; } } + if (!isImportEqualsDeclaration(node) && node.assertClause) { + let hasError = false; + for (const clause of node.assertClause.elements) { + if (!isStringLiteral(clause.value)) { + hasError = true; + error(clause.value, Diagnostics.Import_assertion_values_must_be_string_literal_expressions); + } + } + return !hasError; + } return true; } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index fb4a5dc70d4..01c4e3fa6db 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3369,6 +3369,10 @@ "category": "Error", "code": 2836 }, + "Import assertion values must be string literal expressions.": { + "category": "Error", + "code": 2837 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index 6a20f8069a7..bde236a32b0 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -4024,7 +4024,7 @@ namespace ts { } // @api - function createAssertEntry(name: AssertionKey, value: StringLiteral): AssertEntry { + function createAssertEntry(name: AssertionKey, value: Expression): AssertEntry { const node = createBaseNode(SyntaxKind.AssertEntry); node.name = name; node.value = value; @@ -4033,7 +4033,7 @@ namespace ts { } // @api - function updateAssertEntry(node: AssertEntry, name: AssertionKey, value: StringLiteral): AssertEntry { + function updateAssertEntry(node: AssertEntry, name: AssertionKey, value: Expression): AssertEntry { return node.name !== name || node.value !== value ? update(createAssertEntry(name, value), node) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 4147b4798d4..d7f1901c366 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -7281,7 +7281,7 @@ namespace ts { const pos = getNodePos(); const name = tokenIsIdentifierOrKeyword(token()) ? parseIdentifierName() : parseLiteralLikeNode(SyntaxKind.StringLiteral) as StringLiteral; parseExpected(SyntaxKind.ColonToken); - const value = parseLiteralLikeNode(SyntaxKind.StringLiteral) as StringLiteral; + const value = parseAssignmentExpressionOrHigher(); return finishNode(factory.createAssertEntry(name, value), pos); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index cb46b13c002..d2807b128d8 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3055,7 +3055,7 @@ namespace ts { readonly kind: SyntaxKind.AssertEntry; readonly parent: AssertClause; readonly name: AssertionKey; - readonly value: StringLiteral; + readonly value: Expression; } export interface AssertClause extends Node { @@ -7446,8 +7446,8 @@ namespace ts { updateImportClause(node: ImportClause, isTypeOnly: boolean, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause; createAssertClause(elements: NodeArray, multiLine?: boolean): AssertClause; updateAssertClause(node: AssertClause, elements: NodeArray, multiLine?: boolean): AssertClause; - createAssertEntry(name: AssertionKey, value: StringLiteral): AssertEntry; - updateAssertEntry (node: AssertEntry, name: AssertionKey, value: StringLiteral): AssertEntry; + createAssertEntry(name: AssertionKey, value: Expression): AssertEntry; + updateAssertEntry(node: AssertEntry, name: AssertionKey, value: Expression): AssertEntry; createNamespaceImport(name: Identifier): NamespaceImport; updateNamespaceImport(node: NamespaceImport, name: Identifier): NamespaceImport; createNamespaceExport(name: Identifier): NamespaceExport; diff --git a/src/compiler/visitorPublic.ts b/src/compiler/visitorPublic.ts index 5651ac043d3..6ea70250317 100644 --- a/src/compiler/visitorPublic.ts +++ b/src/compiler/visitorPublic.ts @@ -1090,7 +1090,7 @@ namespace ts { Debug.type(node); return factory.updateAssertEntry(node, nodeVisitor(node.name, visitor, isAssertionKey), - nodeVisitor(node.value, visitor, isStringLiteral)); + nodeVisitor(node.value, visitor, isExpressionNode)); case SyntaxKind.ImportClause: Debug.type(node); diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 5a8a60452b3..d4cccf374ec 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -526,7 +526,7 @@ namespace ts.FindAllReferences { function getTextSpan(node: Node, sourceFile: SourceFile, endNode?: Node): TextSpan { let start = node.getStart(sourceFile); let end = (endNode || node).getEnd(); - if (isStringLiteralLike(node)) { + if (isStringLiteralLike(node) && (end - start) > 2) { Debug.assert(endNode === undefined); start += 1; end -= 1; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 63bad336031..d4aa43152ea 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1652,7 +1652,7 @@ declare namespace ts { readonly kind: SyntaxKind.AssertEntry; readonly parent: AssertClause; readonly name: AssertionKey; - readonly value: StringLiteral; + readonly value: Expression; } export interface AssertClause extends Node { readonly kind: SyntaxKind.AssertClause; @@ -3576,8 +3576,8 @@ declare namespace ts { updateImportClause(node: ImportClause, isTypeOnly: boolean, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause; createAssertClause(elements: NodeArray, multiLine?: boolean): AssertClause; updateAssertClause(node: AssertClause, elements: NodeArray, multiLine?: boolean): AssertClause; - createAssertEntry(name: AssertionKey, value: StringLiteral): AssertEntry; - updateAssertEntry(node: AssertEntry, name: AssertionKey, value: StringLiteral): AssertEntry; + createAssertEntry(name: AssertionKey, value: Expression): AssertEntry; + updateAssertEntry(node: AssertEntry, name: AssertionKey, value: Expression): AssertEntry; createNamespaceImport(name: Identifier): NamespaceImport; updateNamespaceImport(node: NamespaceImport, name: Identifier): NamespaceImport; createNamespaceExport(name: Identifier): NamespaceExport; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index fab7e4ba591..e0c0a8a235c 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1652,7 +1652,7 @@ declare namespace ts { readonly kind: SyntaxKind.AssertEntry; readonly parent: AssertClause; readonly name: AssertionKey; - readonly value: StringLiteral; + readonly value: Expression; } export interface AssertClause extends Node { readonly kind: SyntaxKind.AssertClause; @@ -3576,8 +3576,8 @@ declare namespace ts { updateImportClause(node: ImportClause, isTypeOnly: boolean, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause; createAssertClause(elements: NodeArray, multiLine?: boolean): AssertClause; updateAssertClause(node: AssertClause, elements: NodeArray, multiLine?: boolean): AssertClause; - createAssertEntry(name: AssertionKey, value: StringLiteral): AssertEntry; - updateAssertEntry(node: AssertEntry, name: AssertionKey, value: StringLiteral): AssertEntry; + createAssertEntry(name: AssertionKey, value: Expression): AssertEntry; + updateAssertEntry(node: AssertEntry, name: AssertionKey, value: Expression): AssertEntry; createNamespaceImport(name: Identifier): NamespaceImport; updateNamespaceImport(node: NamespaceImport, name: Identifier): NamespaceImport; createNamespaceExport(name: Identifier): NamespaceExport; diff --git a/tests/baselines/reference/importAssertionNonstring.errors.txt b/tests/baselines/reference/importAssertionNonstring.errors.txt new file mode 100644 index 00000000000..a619f9f2d4f --- /dev/null +++ b/tests/baselines/reference/importAssertionNonstring.errors.txt @@ -0,0 +1,32 @@ +tests/cases/compiler/mod.mts(1,52): error TS2837: Import assertion values must be string literal expressions. +tests/cases/compiler/mod.mts(3,52): error TS2837: Import assertion values must be string literal expressions. +tests/cases/compiler/mod.mts(5,52): error TS2837: Import assertion values must be string literal expressions. +tests/cases/compiler/mod.mts(7,52): error TS2837: Import assertion values must be string literal expressions. +tests/cases/compiler/mod.mts(9,52): error TS2837: Import assertion values must be string literal expressions. +tests/cases/compiler/mod.mts(11,66): error TS2837: Import assertion values must be string literal expressions. + + +==== tests/cases/compiler/mod.mts (6 errors) ==== + import * as thing1 from "./mod.mjs" assert {field: 0}; + ~ +!!! error TS2837: Import assertion values must be string literal expressions. + + import * as thing2 from "./mod.mjs" assert {field: `a`}; + ~~~ +!!! error TS2837: Import assertion values must be string literal expressions. + + import * as thing3 from "./mod.mjs" assert {field: /a/g}; + ~~~~ +!!! error TS2837: Import assertion values must be string literal expressions. + + import * as thing4 from "./mod.mjs" assert {field: ["a"]}; + ~~~~~ +!!! error TS2837: Import assertion values must be string literal expressions. + + import * as thing5 from "./mod.mjs" assert {field: { a: 0 }}; + ~~~~~~~~ +!!! error TS2837: Import assertion values must be string literal expressions. + + import * as thing6 from "./mod.mjs" assert {type: "json", field: 0..toString()} + ~~~~~~~~~~~~~ +!!! error TS2837: Import assertion values must be string literal expressions. \ No newline at end of file diff --git a/tests/baselines/reference/importAssertionNonstring.js b/tests/baselines/reference/importAssertionNonstring.js new file mode 100644 index 00000000000..18b6c7fdee9 --- /dev/null +++ b/tests/baselines/reference/importAssertionNonstring.js @@ -0,0 +1,15 @@ +//// [mod.mts] +import * as thing1 from "./mod.mjs" assert {field: 0}; + +import * as thing2 from "./mod.mjs" assert {field: `a`}; + +import * as thing3 from "./mod.mjs" assert {field: /a/g}; + +import * as thing4 from "./mod.mjs" assert {field: ["a"]}; + +import * as thing5 from "./mod.mjs" assert {field: { a: 0 }}; + +import * as thing6 from "./mod.mjs" assert {type: "json", field: 0..toString()} + +//// [mod.mjs] +export {}; diff --git a/tests/baselines/reference/importAssertionNonstring.symbols b/tests/baselines/reference/importAssertionNonstring.symbols new file mode 100644 index 00000000000..386888efb51 --- /dev/null +++ b/tests/baselines/reference/importAssertionNonstring.symbols @@ -0,0 +1,22 @@ +=== tests/cases/compiler/mod.mts === +import * as thing1 from "./mod.mjs" assert {field: 0}; +>thing1 : Symbol(thing1, Decl(mod.mts, 0, 6)) + +import * as thing2 from "./mod.mjs" assert {field: `a`}; +>thing2 : Symbol(thing2, Decl(mod.mts, 2, 6)) + +import * as thing3 from "./mod.mjs" assert {field: /a/g}; +>thing3 : Symbol(thing3, Decl(mod.mts, 4, 6)) + +import * as thing4 from "./mod.mjs" assert {field: ["a"]}; +>thing4 : Symbol(thing4, Decl(mod.mts, 6, 6)) + +import * as thing5 from "./mod.mjs" assert {field: { a: 0 }}; +>thing5 : Symbol(thing5, Decl(mod.mts, 8, 6)) +>a : Symbol(a, Decl(mod.mts, 8, 52)) + +import * as thing6 from "./mod.mjs" assert {type: "json", field: 0..toString()} +>thing6 : Symbol(thing6, Decl(mod.mts, 10, 6)) +>0..toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) +>toString : Symbol(Number.toString, Decl(lib.es5.d.ts, --, --)) + diff --git a/tests/baselines/reference/importAssertionNonstring.types b/tests/baselines/reference/importAssertionNonstring.types new file mode 100644 index 00000000000..c165c0de610 --- /dev/null +++ b/tests/baselines/reference/importAssertionNonstring.types @@ -0,0 +1,36 @@ +=== tests/cases/compiler/mod.mts === +import * as thing1 from "./mod.mjs" assert {field: 0}; +>thing1 : typeof thing1 +>field : any + +import * as thing2 from "./mod.mjs" assert {field: `a`}; +>thing2 : typeof thing1 +>field : any + +import * as thing3 from "./mod.mjs" assert {field: /a/g}; +>thing3 : typeof thing1 +>field : any +>/a/g : RegExp + +import * as thing4 from "./mod.mjs" assert {field: ["a"]}; +>thing4 : typeof thing1 +>field : any +>["a"] : string[] +>"a" : "a" + +import * as thing5 from "./mod.mjs" assert {field: { a: 0 }}; +>thing5 : typeof thing1 +>field : any +>{ a: 0 } : { a: number; } +>a : number +>0 : 0 + +import * as thing6 from "./mod.mjs" assert {type: "json", field: 0..toString()} +>thing6 : typeof thing1 +>type : any +>field : any +>0..toString() : string +>0..toString : (radix?: number) => string +>0. : 0 +>toString : (radix?: number) => string + diff --git a/tests/cases/compiler/importAssertionNonstring.ts b/tests/cases/compiler/importAssertionNonstring.ts new file mode 100644 index 00000000000..5063e1b4922 --- /dev/null +++ b/tests/cases/compiler/importAssertionNonstring.ts @@ -0,0 +1,13 @@ +// @module: nodenext +// @filename: mod.mts +import * as thing1 from "./mod.mjs" assert {field: 0}; + +import * as thing2 from "./mod.mjs" assert {field: `a`}; + +import * as thing3 from "./mod.mjs" assert {field: /a/g}; + +import * as thing4 from "./mod.mjs" assert {field: ["a"]}; + +import * as thing5 from "./mod.mjs" assert {field: { a: 0 }}; + +import * as thing6 from "./mod.mjs" assert {type: "json", field: 0..toString()} \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesNonStringImportAssertion.ts b/tests/cases/fourslash/getOccurrencesNonStringImportAssertion.ts new file mode 100644 index 00000000000..4ab0e714625 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesNonStringImportAssertion.ts @@ -0,0 +1,8 @@ +/// + +// @module: nodenext +////import * as react from "react" assert { cache: /**/0 }; +////react.Children; + +goTo.marker(); +verify.occurrencesAtPositionCount(0);