diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index fb31e3dbdc518..65681f5566f9d 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1491,6 +1491,8 @@ namespace Parser { var identifiers: Map; var identifierCount: number; + // TODO(jakebailey): This type is a lie; this value actually contains the result + // of ORing a bunch of `1 << ParsingContext.XYZ`. var parsingContext: ParsingContext; var notParenthesizedArrow: Set | undefined; @@ -2872,9 +2874,13 @@ namespace Parser { return tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.OpenBraceToken; case ParsingContext.JsxChildren: return true; + case ParsingContext.JSDocComment: + return true; + case ParsingContext.Count: + return Debug.fail("ParsingContext.Count used as a context"); // Not a real context, only a marker. + default: + Debug.assertNever(parsingContext, "Non-exhaustive case in 'isListElement'."); } - - return Debug.fail("Non-exhaustive case in 'isListElement'."); } function isValidHeritageClauseObjectLiteral() { @@ -3010,6 +3016,9 @@ namespace Parser { // True if positioned at element or terminator of the current list or any enclosing list function isInSomeParsingContext(): boolean { + // We should be in at least one parsing context, be it SourceElements while parsing + // a SourceFile, or JSDocComment when lazily parsing JSDoc. + Debug.assert(parsingContext, "Missing parsing context"); for (let kind = 0; kind < ParsingContext.Count; kind++) { if (parsingContext & (1 << kind)) { if (isListElement(kind, /*inErrorRecovery*/ true) || isListTerminator(kind)) { @@ -3385,6 +3394,7 @@ namespace Parser { case ParsingContext.JsxAttributes: return parseErrorAtCurrentToken(Diagnostics.Identifier_expected); case ParsingContext.JsxChildren: return parseErrorAtCurrentToken(Diagnostics.Identifier_expected); case ParsingContext.AssertEntries: return parseErrorAtCurrentToken(Diagnostics.Identifier_or_string_literal_expected); // AssertionKey. + case ParsingContext.JSDocComment: return parseErrorAtCurrentToken(Diagnostics.Identifier_expected); case ParsingContext.Count: return Debug.fail("ParsingContext.Count used as a context"); // Not a real context, only a marker. default: Debug.assertNever(context); } @@ -7289,6 +7299,8 @@ namespace Parser { function tryReuseAmbientDeclaration(pos: number): Statement | undefined { return doInsideOfContext(NodeFlags.Ambient, () => { + // TODO(jakebailey): this is totally wrong; `parsingContext` is the result of ORing a bunch of `1 << ParsingContext.XYZ`. + // The enum should really be a bunch of flags. const node = currentNode(parsingContext, pos); if (node) { return consumeNode(node) as Statement; @@ -8492,7 +8504,8 @@ namespace Parser { TupleElementTypes, // Element types in tuple element type list HeritageClauses, // Heritage clauses for a class or interface declaration. ImportOrExportSpecifiers, // Named import clause's import specifier list, - AssertEntries, // Import entries list. + AssertEntries, // Import entries list. + JSDocComment, // Parsing via JSDocParser Count // Number of parsing contexts } @@ -8598,6 +8611,9 @@ namespace Parser { } function parseJSDocCommentWorker(start = 0, length: number | undefined): JSDoc | undefined { + const saveParsingContext = parsingContext; + parsingContext |= 1 << ParsingContext.JSDocComment; + const content = sourceText; const end = length === undefined ? content.length : start + length; length = end - start; @@ -8620,7 +8636,11 @@ namespace Parser { const parts: JSDocComment[] = []; // + 3 for leading /**, - 5 in total for /** */ - return scanner.scanRange(start + 3, length - 5, () => { + const result = scanner.scanRange(start + 3, length - 5, doJSDocScan); + parsingContext = saveParsingContext; + return result; + + function doJSDocScan() { // Initially we can parse out a tag. We also have seen a starting asterisk. // This is so that /** * @type */ doesn't parse. let state = JSDocState.SawAsterisk; @@ -8726,7 +8746,7 @@ namespace Parser { if (parts.length && tags) Debug.assertIsDefined(commentsPos, "having parsed tags implies that the end of the comment span should be set"); const tagsArray = tags && createNodeArray(tags, tagsPos, tagsEnd); return finishNode(factory.createJSDocComment(parts.length ? createNodeArray(parts, start, commentsPos) : trimmedComments.length ? trimmedComments : undefined, tagsArray), start, end); - }); + } function removeLeadingNewlines(comments: string[]) { while (comments.length && (comments[0] === "\n" || comments[0] === "\r")) { diff --git a/tests/baselines/reference/jsDocDontBreakWithNamespaces.baseline b/tests/baselines/reference/jsDocDontBreakWithNamespaces.baseline index 6193417fbcfbe..5f2b3405c4b32 100644 --- a/tests/baselines/reference/jsDocDontBreakWithNamespaces.baseline +++ b/tests/baselines/reference/jsDocDontBreakWithNamespaces.baseline @@ -25,7 +25,7 @@ // zee(''); // ^ // | ---------------------------------------------------------------------- -// | zee(**arg0: any**, arg1: any, arg2: any): any +// | zee(**arg0: any**, arg1: any): any // | ---------------------------------------------------------------------- [ @@ -259,30 +259,6 @@ ], "isOptional": false, "isRest": false - }, - { - "name": "arg2", - "documentation": [], - "displayParts": [ - { - "text": "arg2", - "kind": "parameterName" - }, - { - "text": ":", - "kind": "punctuation" - }, - { - "text": " ", - "kind": "space" - }, - { - "text": "any", - "kind": "keyword" - } - ], - "isOptional": false, - "isRest": false } ], "documentation": [], diff --git a/tests/baselines/reference/jsdocFunctionTypeFalsePositive.errors.txt b/tests/baselines/reference/jsdocFunctionTypeFalsePositive.errors.txt index b50b49c2e4572..f3bf77a4156b1 100644 --- a/tests/baselines/reference/jsdocFunctionTypeFalsePositive.errors.txt +++ b/tests/baselines/reference/jsdocFunctionTypeFalsePositive.errors.txt @@ -1,24 +1,12 @@ +/a.js(1,13): error TS1098: Type parameter list cannot be empty. /a.js(1,14): error TS1139: Type parameter declaration expected. -/a.js(1,17): error TS1003: Identifier expected. -/a.js(1,17): error TS1110: Type expected. -/a.js(1,17): error TS8024: JSDoc '@param' tag has name '', but there is no parameter with that name. -/a.js(1,18): error TS1005: '>' expected. -/a.js(1,18): error TS1005: '}' expected. -==== /a.js (6 errors) ==== +==== /a.js (2 errors) ==== /** @param {<} x */ + ~~ +!!! error TS1098: Type parameter list cannot be empty. ~ !!! error TS1139: Type parameter declaration expected. - -!!! error TS1003: Identifier expected. - -!!! error TS1110: Type expected. - -!!! error TS8024: JSDoc '@param' tag has name '', but there is no parameter with that name. - -!!! error TS1005: '>' expected. - -!!! error TS1005: '}' expected. function f(x) {} \ No newline at end of file diff --git a/tests/baselines/reference/jsdocFunctionTypeFalsePositive.types b/tests/baselines/reference/jsdocFunctionTypeFalsePositive.types index 29b3b4d2e1c80..a1b20646143b5 100644 --- a/tests/baselines/reference/jsdocFunctionTypeFalsePositive.types +++ b/tests/baselines/reference/jsdocFunctionTypeFalsePositive.types @@ -1,6 +1,6 @@ === /a.js === /** @param {<} x */ function f(x) {} ->f : (x: any) => void ->x : any +>f : (x: () => any) => void +>x : () => any diff --git a/tests/cases/fourslash/jsdocWithBarInTypeLiteral.ts b/tests/cases/fourslash/jsdocWithBarInTypeLiteral.ts new file mode 100644 index 0000000000000..f291335ca4fae --- /dev/null +++ b/tests/cases/fourslash/jsdocWithBarInTypeLiteral.ts @@ -0,0 +1,11 @@ +/// + +// @filename: index.js +//// class I18n { +//// /** +//// * @param {{dot|fulltext}} [stringMode] - which mode our translation keys use +//// */ +//// constructor(options = {}) {} +//// } + +verify.encodedSyntacticClassificationsLength(69);