Skip to content

Commit e450c46

Browse files
authored
fix(59011): TypeScript generates invalid types if @import tags are spread over multiple lines (#59026)
1 parent 64f89e7 commit e450c46

19 files changed

+445
-16
lines changed

src/compiler/parser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2637,7 +2637,7 @@ namespace Parser {
26372637
function createIdentifier(isIdentifier: boolean, diagnosticMessage?: DiagnosticMessage, privateIdentifierDiagnosticMessage?: DiagnosticMessage): Identifier {
26382638
if (isIdentifier) {
26392639
identifierCount++;
2640-
const pos = getNodePos();
2640+
const pos = scanner.hasPrecedingJSDocLeadingAsterisks() ? scanner.getTokenStart() : getNodePos();
26412641
// Store original token kind if it is not just an Identifier so we can report appropriate error later in type checker
26422642
const originalKeywordKind = token();
26432643
const text = internIdentifier(scanner.getTokenValue());

src/compiler/scanner.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ export interface Scanner {
6565
hasPrecedingLineBreak(): boolean;
6666
/** @internal */
6767
hasPrecedingJSDocComment(): boolean;
68+
/** @internal */
69+
hasPrecedingJSDocLeadingAsterisks(): boolean;
6870
isIdentifier(): boolean;
6971
isReservedWord(): boolean;
7072
isUnterminated(): boolean;
@@ -1059,6 +1061,7 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean
10591061
hasExtendedUnicodeEscape: () => (tokenFlags & TokenFlags.ExtendedUnicodeEscape) !== 0,
10601062
hasPrecedingLineBreak: () => (tokenFlags & TokenFlags.PrecedingLineBreak) !== 0,
10611063
hasPrecedingJSDocComment: () => (tokenFlags & TokenFlags.PrecedingJSDocComment) !== 0,
1064+
hasPrecedingJSDocLeadingAsterisks: () => (tokenFlags & TokenFlags.PrecedingJSDocLeadingAsterisks) !== 0,
10621065
isIdentifier: () => token === SyntaxKind.Identifier || token > SyntaxKind.LastReservedWord,
10631066
isReservedWord: () => token >= SyntaxKind.FirstReservedWord && token <= SyntaxKind.LastReservedWord,
10641067
isUnterminated: () => (tokenFlags & TokenFlags.Unterminated) !== 0,
@@ -1874,7 +1877,6 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean
18741877
function scan(): SyntaxKind {
18751878
fullStartPos = pos;
18761879
tokenFlags = TokenFlags.None;
1877-
let asteriskSeen = false;
18781880
while (true) {
18791881
tokenStart = pos;
18801882
if (pos >= end) {
@@ -1995,9 +1997,13 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean
19951997
return pos += 2, token = SyntaxKind.AsteriskAsteriskToken;
19961998
}
19971999
pos++;
1998-
if (skipJsDocLeadingAsterisks && !asteriskSeen && (tokenFlags & TokenFlags.PrecedingLineBreak)) {
2000+
if (
2001+
skipJsDocLeadingAsterisks &&
2002+
(tokenFlags & TokenFlags.PrecedingJSDocLeadingAsterisks) === 0 &&
2003+
(tokenFlags & TokenFlags.PrecedingLineBreak)
2004+
) {
19992005
// decoration at the start of a JSDoc comment line
2000-
asteriskSeen = true;
2006+
tokenFlags |= TokenFlags.PrecedingJSDocLeadingAsterisks;
20012007
continue;
20022008
}
20032009
return token = SyntaxKind.AsteriskToken;

src/compiler/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2812,6 +2812,8 @@ export const enum TokenFlags {
28122812
/** @internal */
28132813
ContainsInvalidSeparator = 1 << 14, // e.g. `0_1`
28142814
/** @internal */
2815+
PrecedingJSDocLeadingAsterisks = 1 << 15,
2816+
/** @internal */
28152817
BinaryOrOctalSpecifier = BinarySpecifier | OctalSpecifier,
28162818
/** @internal */
28172819
WithSpecifier = HexSpecifier | BinaryOrOctalSpecifier,
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//// [tests/cases/conformance/jsdoc/importTag18.ts] ////
2+
3+
//// [a.ts]
4+
export interface Foo {}
5+
6+
//// [b.js]
7+
/**
8+
* @import {
9+
* Foo
10+
* } from "./a"
11+
*/
12+
13+
/**
14+
* @param {Foo} a
15+
*/
16+
export function foo(a) {}
17+
18+
19+
20+
21+
//// [a.d.ts]
22+
export interface Foo {
23+
}
24+
//// [b.d.ts]
25+
/**
26+
* @import {
27+
* Foo
28+
* } from "./a"
29+
*/
30+
/**
31+
* @param {Foo} a
32+
*/
33+
export function foo(a: Foo): void;
34+
import type { Foo } from "./a";
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//// [tests/cases/conformance/jsdoc/importTag18.ts] ////
2+
3+
=== a.ts ===
4+
export interface Foo {}
5+
>Foo : Symbol(Foo, Decl(a.ts, 0, 0))
6+
7+
=== b.js ===
8+
/**
9+
* @import {
10+
* Foo
11+
* } from "./a"
12+
*/
13+
14+
/**
15+
* @param {Foo} a
16+
*/
17+
export function foo(a) {}
18+
>foo : Symbol(foo, Decl(b.js, 0, 0))
19+
>a : Symbol(a, Decl(b.js, 9, 20))
20+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//// [tests/cases/conformance/jsdoc/importTag18.ts] ////
2+
3+
=== a.ts ===
4+
5+
export interface Foo {}
6+
7+
=== b.js ===
8+
/**
9+
* @import {
10+
* Foo
11+
* } from "./a"
12+
*/
13+
14+
/**
15+
* @param {Foo} a
16+
*/
17+
export function foo(a) {}
18+
>foo : (a: Foo) => void
19+
> : ^ ^^^^^^^^^^^^^^
20+
>a : Foo
21+
> : ^^^
22+
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//// [tests/cases/conformance/jsdoc/importTag19.ts] ////
2+
3+
//// [a.ts]
4+
export interface Foo {}
5+
6+
//// [b.js]
7+
/**
8+
* @import { Foo }
9+
* from "./a"
10+
*/
11+
12+
/**
13+
* @param {Foo} a
14+
*/
15+
export function foo(a) {}
16+
17+
18+
19+
20+
//// [a.d.ts]
21+
export interface Foo {
22+
}
23+
//// [b.d.ts]
24+
/**
25+
* @import { Foo }
26+
* from "./a"
27+
*/
28+
/**
29+
* @param {Foo} a
30+
*/
31+
export function foo(a: Foo): void;
32+
import type { Foo } from "./a";
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//// [tests/cases/conformance/jsdoc/importTag19.ts] ////
2+
3+
=== a.ts ===
4+
export interface Foo {}
5+
>Foo : Symbol(Foo, Decl(a.ts, 0, 0))
6+
7+
=== b.js ===
8+
/**
9+
* @import { Foo }
10+
* from "./a"
11+
*/
12+
13+
/**
14+
* @param {Foo} a
15+
*/
16+
export function foo(a) {}
17+
>foo : Symbol(foo, Decl(b.js, 0, 0))
18+
>a : Symbol(a, Decl(b.js, 8, 20))
19+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//// [tests/cases/conformance/jsdoc/importTag19.ts] ////
2+
3+
=== a.ts ===
4+
5+
export interface Foo {}
6+
7+
=== b.js ===
8+
/**
9+
* @import { Foo }
10+
* from "./a"
11+
*/
12+
13+
/**
14+
* @param {Foo} a
15+
*/
16+
export function foo(a) {}
17+
>foo : (a: Foo) => void
18+
> : ^ ^^^^^^^^^^^^^^
19+
>a : Foo
20+
> : ^^^
21+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//// [tests/cases/conformance/jsdoc/importTag20.ts] ////
2+
3+
//// [a.ts]
4+
export interface Foo {}
5+
6+
//// [b.js]
7+
/**
8+
* @import
9+
* { Foo
10+
* } from './a'
11+
*/
12+
13+
/**
14+
* @param {Foo} a
15+
*/
16+
export function foo(a) {}
17+
18+
19+
20+
21+
//// [a.d.ts]
22+
export interface Foo {
23+
}
24+
//// [b.d.ts]
25+
/**
26+
* @import
27+
* { Foo
28+
* } from './a'
29+
*/
30+
/**
31+
* @param {Foo} a
32+
*/
33+
export function foo(a: Foo): void;
34+
import type { Foo } from './a';

0 commit comments

Comments
 (0)