From 6fd725f3eae6595303071395417311d01a371cee Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Thu, 16 Aug 2018 17:16:09 -0600 Subject: [PATCH] Skip whitespace or asterisk in JSDoc param type and name (#26067) --- src/compiler/parser.ts | 25 +++++++- src/compiler/scanner.ts | 2 + .../reference/paramTagWrapping.errors.txt | 58 +++++++++++++++++++ .../reference/paramTagWrapping.symbols | 39 +++++++++++++ .../reference/paramTagWrapping.types | 47 +++++++++++++++ .../conformance/jsdoc/paramTagWrapping.ts | 35 +++++++++++ 6 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/paramTagWrapping.errors.txt create mode 100644 tests/baselines/reference/paramTagWrapping.symbols create mode 100644 tests/baselines/reference/paramTagWrapping.types create mode 100644 tests/cases/conformance/jsdoc/paramTagWrapping.ts diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 87c638d9a0..096c3f1635 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -6507,6 +6507,25 @@ namespace ts { } } + function skipWhitespaceOrAsterisk(): void { + if (token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { + if (lookAhead(isNextNonwhitespaceTokenEndOfFile)) { + return; // Don't skip whitespace prior to EoF (or end of comment) - that shouldn't be included in any node's range + } + } + + let precedingLineBreak = scanner.hasPrecedingLineBreak(); + while ((precedingLineBreak && token() === SyntaxKind.AsteriskToken) || token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { + if (token() === SyntaxKind.NewLineTrivia) { + precedingLineBreak = true; + } + else if (token() === SyntaxKind.AsteriskToken) { + precedingLineBreak = false; + } + nextJSDocToken(); + } + } + function parseTag(indent: number) { Debug.assert(token() === SyntaxKind.AtToken); const atToken = createNode(SyntaxKind.AtToken, scanner.getTokenPos()); @@ -6514,7 +6533,7 @@ namespace ts { nextJSDocToken(); const tagName = parseJSDocIdentifierName(); - skipWhitespace(); + skipWhitespaceOrAsterisk(); let tag: JSDocTag | undefined; switch (tagName.escapedText) { @@ -6658,7 +6677,7 @@ namespace ts { } function tryParseTypeExpression(): JSDocTypeExpression | undefined { - skipWhitespace(); + skipWhitespaceOrAsterisk(); return token() === SyntaxKind.OpenBraceToken ? parseJSDocTypeExpression() : undefined; } @@ -6698,7 +6717,7 @@ namespace ts { function parseParameterOrPropertyTag(atToken: AtToken, tagName: Identifier, target: PropertyLikeParse, indent: number | undefined): JSDocParameterTag | JSDocPropertyTag { let typeExpression = tryParseTypeExpression(); let isNameFirst = !typeExpression; - skipWhitespace(); + skipWhitespaceOrAsterisk(); const { name, isBracketed } = parseBracketNameInPropertyAndParamTag(); skipWhitespace(); diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index af74d69cee..61ce8330bf 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -1933,6 +1933,7 @@ namespace ts { function scanJSDocToken(): JsDocSyntaxKind { startPos = tokenPos = pos; + tokenFlags = 0; if (pos >= end) { return token = SyntaxKind.EndOfFileToken; } @@ -1952,6 +1953,7 @@ namespace ts { return token = SyntaxKind.AtToken; case CharacterCodes.lineFeed: case CharacterCodes.carriageReturn: + tokenFlags |= TokenFlags.PrecedingLineBreak; return token = SyntaxKind.NewLineTrivia; case CharacterCodes.asterisk: return token = SyntaxKind.AsteriskToken; diff --git a/tests/baselines/reference/paramTagWrapping.errors.txt b/tests/baselines/reference/paramTagWrapping.errors.txt new file mode 100644 index 0000000000..48100f0e74 --- /dev/null +++ b/tests/baselines/reference/paramTagWrapping.errors.txt @@ -0,0 +1,58 @@ +tests/cases/conformance/jsdoc/bad.js(2,11): error TS1003: Identifier expected. +tests/cases/conformance/jsdoc/bad.js(2,11): error TS8024: JSDoc '@param' tag has name '', but there is no parameter with that name. +tests/cases/conformance/jsdoc/bad.js(5,4): error TS1003: Identifier expected. +tests/cases/conformance/jsdoc/bad.js(5,4): error TS8024: JSDoc '@param' tag has name '', but there is no parameter with that name. +tests/cases/conformance/jsdoc/bad.js(6,19): error TS1003: Identifier expected. +tests/cases/conformance/jsdoc/bad.js(6,19): error TS8024: JSDoc '@param' tag has name '', but there is no parameter with that name. +tests/cases/conformance/jsdoc/bad.js(9,14): error TS7006: Parameter 'x' implicitly has an 'any' type. +tests/cases/conformance/jsdoc/bad.js(9,17): error TS7006: Parameter 'y' implicitly has an 'any' type. +tests/cases/conformance/jsdoc/bad.js(9,20): error TS7006: Parameter 'z' implicitly has an 'any' type. + + +==== tests/cases/conformance/jsdoc/good.js (0 errors) ==== + /** + * @param + * {number} x Arg x. + * @param {number} + * y Arg y. + * @param {number} z + * Arg z. + */ + function good(x, y, z) { + } + + good(1, 2, 3) + + +==== tests/cases/conformance/jsdoc/bad.js (9 errors) ==== + /** + * @param * + +!!! error TS1003: Identifier expected. + +!!! error TS8024: JSDoc '@param' tag has name '', but there is no parameter with that name. + * {number} x Arg x. + * @param {number} + * * y Arg y. + +!!! error TS1003: Identifier expected. + +!!! error TS8024: JSDoc '@param' tag has name '', but there is no parameter with that name. + * @param {number} * z + +!!! error TS1003: Identifier expected. + +!!! error TS8024: JSDoc '@param' tag has name '', but there is no parameter with that name. + * Arg z. + */ + function bad(x, y, z) { + ~ +!!! error TS7006: Parameter 'x' implicitly has an 'any' type. + ~ +!!! error TS7006: Parameter 'y' implicitly has an 'any' type. + ~ +!!! error TS7006: Parameter 'z' implicitly has an 'any' type. + } + + bad(1, 2, 3) + \ No newline at end of file diff --git a/tests/baselines/reference/paramTagWrapping.symbols b/tests/baselines/reference/paramTagWrapping.symbols new file mode 100644 index 0000000000..8f11ada24a --- /dev/null +++ b/tests/baselines/reference/paramTagWrapping.symbols @@ -0,0 +1,39 @@ +=== tests/cases/conformance/jsdoc/good.js === +/** + * @param + * {number} x Arg x. + * @param {number} + * y Arg y. + * @param {number} z + * Arg z. + */ +function good(x, y, z) { +>good : Symbol(good, Decl(good.js, 0, 0)) +>x : Symbol(x, Decl(good.js, 8, 14)) +>y : Symbol(y, Decl(good.js, 8, 16)) +>z : Symbol(z, Decl(good.js, 8, 19)) +} + +good(1, 2, 3) +>good : Symbol(good, Decl(good.js, 0, 0)) + + +=== tests/cases/conformance/jsdoc/bad.js === +/** + * @param * + * {number} x Arg x. + * @param {number} + * * y Arg y. + * @param {number} * z + * Arg z. + */ +function bad(x, y, z) { +>bad : Symbol(bad, Decl(bad.js, 0, 0)) +>x : Symbol(x, Decl(bad.js, 8, 13)) +>y : Symbol(y, Decl(bad.js, 8, 15)) +>z : Symbol(z, Decl(bad.js, 8, 18)) +} + +bad(1, 2, 3) +>bad : Symbol(bad, Decl(bad.js, 0, 0)) + diff --git a/tests/baselines/reference/paramTagWrapping.types b/tests/baselines/reference/paramTagWrapping.types new file mode 100644 index 0000000000..362a319ddd --- /dev/null +++ b/tests/baselines/reference/paramTagWrapping.types @@ -0,0 +1,47 @@ +=== tests/cases/conformance/jsdoc/good.js === +/** + * @param + * {number} x Arg x. + * @param {number} + * y Arg y. + * @param {number} z + * Arg z. + */ +function good(x, y, z) { +>good : (x: number, y: number, z: number) => void +>x : number +>y : number +>z : number +} + +good(1, 2, 3) +>good(1, 2, 3) : void +>good : (x: number, y: number, z: number) => void +>1 : 1 +>2 : 2 +>3 : 3 + + +=== tests/cases/conformance/jsdoc/bad.js === +/** + * @param * + * {number} x Arg x. + * @param {number} + * * y Arg y. + * @param {number} * z + * Arg z. + */ +function bad(x, y, z) { +>bad : (x: any, y: any, z: any) => void +>x : any +>y : any +>z : any +} + +bad(1, 2, 3) +>bad(1, 2, 3) : void +>bad : (x: any, y: any, z: any) => void +>1 : 1 +>2 : 2 +>3 : 3 + diff --git a/tests/cases/conformance/jsdoc/paramTagWrapping.ts b/tests/cases/conformance/jsdoc/paramTagWrapping.ts new file mode 100644 index 0000000000..5f3a41adb5 --- /dev/null +++ b/tests/cases/conformance/jsdoc/paramTagWrapping.ts @@ -0,0 +1,35 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @strict: true + +// @Filename: good.js + +/** + * @param + * {number} x Arg x. + * @param {number} + * y Arg y. + * @param {number} z + * Arg z. + */ +function good(x, y, z) { +} + +good(1, 2, 3) + + +// @Filename: bad.js + +/** + * @param * + * {number} x Arg x. + * @param {number} + * * y Arg y. + * @param {number} * z + * Arg z. + */ +function bad(x, y, z) { +} + +bad(1, 2, 3)