Skip to content

Commit

Permalink
Skip asterisks after newline when parsing JSDoc types (#26528)
Browse files Browse the repository at this point in the history
* Skip asterisks after newline when parsing JSDoc types

* Single boolean expression

* Test for parsing and printing multiline function signatures with *
  • Loading branch information
tschaub authored and sandersn committed Sep 4, 2018
1 parent 64ac5a5 commit 262ea5b
Show file tree
Hide file tree
Showing 8 changed files with 641 additions and 12 deletions.
2 changes: 2 additions & 0 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2377,8 +2377,10 @@ namespace ts {
}

function parseJSDocType(): TypeNode {
scanner.setInJSDocType(true);
const dotdotdot = parseOptionalToken(SyntaxKind.DotDotDotToken);
let type = parseTypeOrTypePredicate();
scanner.setInJSDocType(false);
if (dotdotdot) {
const variadic = createNode(SyntaxKind.JSDocVariadicType, dotdotdot.pos) as JSDocVariadicType;
variadic.type = type;
Expand Down
15 changes: 15 additions & 0 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ namespace ts {
setScriptTarget(scriptTarget: ScriptTarget): void;
setLanguageVariant(variant: LanguageVariant): void;
setTextPos(textPos: number): void;
/* @internal */
setInJSDocType(inType: boolean): void;
// Invokes the provided callback then unconditionally restores the scanner to the state it
// was in immediately prior to invoking the callback. The result of invoking the callback
// is returned from this function.
Expand Down Expand Up @@ -824,6 +826,8 @@ namespace ts {
let tokenValue!: string;
let tokenFlags: TokenFlags;

let inJSDocType = 0;

setText(text, start, length);

return {
Expand Down Expand Up @@ -854,6 +858,7 @@ namespace ts {
setLanguageVariant,
setOnError,
setTextPos,
setInJSDocType,
tryScan,
lookAhead,
scanRange,
Expand Down Expand Up @@ -1350,6 +1355,7 @@ namespace ts {
function scan(): SyntaxKind {
startPos = pos;
tokenFlags = 0;
let asteriskSeen = false;
while (true) {
tokenPos = pos;
if (pos >= end) {
Expand Down Expand Up @@ -1447,6 +1453,11 @@ namespace ts {
return pos += 2, token = SyntaxKind.AsteriskAsteriskToken;
}
pos++;
if (inJSDocType && !asteriskSeen && (tokenFlags & TokenFlags.PrecedingLineBreak)) {
// decoration at the start of a JSDoc comment line
asteriskSeen = true;
continue;
}
return token = SyntaxKind.AsteriskToken;
case CharacterCodes.plus:
if (text.charCodeAt(pos + 1) === CharacterCodes.plus) {
Expand Down Expand Up @@ -2078,5 +2089,9 @@ namespace ts {
tokenValue = undefined!;
tokenFlags = 0;
}

function setInJSDocType(inType: boolean) {
inJSDocType += inType ? 1 : -1;
}
}
}
13 changes: 12 additions & 1 deletion src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -490,12 +490,23 @@ namespace ts {
return getTextOfNodeFromSourceText(sourceFile.text, node, includeTrivia);
}

function isJSDocTypeExpressionOrChild(node: Node): boolean {
return node.kind === SyntaxKind.JSDocTypeExpression || (node.parent && isJSDocTypeExpressionOrChild(node.parent));
}

export function getTextOfNodeFromSourceText(sourceText: string, node: Node, includeTrivia = false): string {
if (nodeIsMissing(node)) {
return "";
}

return sourceText.substring(includeTrivia ? node.pos : skipTrivia(sourceText, node.pos), node.end);
let text = sourceText.substring(includeTrivia ? node.pos : skipTrivia(sourceText, node.pos), node.end);

if (isJSDocTypeExpressionOrChild(node)) {
// strip space + asterisk at line start
text = text.replace(/(^|\r?\n|\r)\s*\*\s*/g, "$1");
}

return text;
}

export function getTextOfNode(node: Node, includeTrivia = false): string {
Expand Down
136 changes: 136 additions & 0 deletions tests/baselines/reference/typedefTagWrapping.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
tests/cases/conformance/jsdoc/mod7.js(5,7): error TS1110: Type expected.
tests/cases/conformance/jsdoc/mod7.js(8,4): error TS1110: Type expected.


==== tests/cases/conformance/jsdoc/mod1.js (0 errors) ====
/**
* @typedef {function(string): boolean}
* Type1
*/

/**
* Tries to use a type whose name is on a different
* line than the typedef tag.
* @param {Type1} func The function to call.
* @param {string} arg The argument to call it with.
* @returns {boolean} The return.
*/
function callIt(func, arg) {
return func(arg);
}

==== tests/cases/conformance/jsdoc/mod2.js (0 errors) ====
/**
* @typedef {{
* num: number,
* str: string,
* boo: boolean
* }} Type2
*/

/**
* Makes use of a type with a multiline type expression.
* @param {Type2} obj The object.
* @returns {string|number} The return.
*/
function check(obj) {
return obj.boo ? obj.num : obj.str;
}

==== tests/cases/conformance/jsdoc/mod3.js (0 errors) ====
/**
* A function whose signature is very long.
*
* @typedef {function(boolean, string, number):
* (string|number)} StringOrNumber1
*/

/**
* Makes use of a function type with a long signature.
* @param {StringOrNumber1} func The function.
* @param {boolean} bool The condition.
* @param {string} str The string.
* @param {number} num The number.
* @returns {string|number} The return.
*/
function use1(func, bool, str, num) {
return func(bool, str, num)
}

==== tests/cases/conformance/jsdoc/mod4.js (0 errors) ====
/**
* A function whose signature is very long.
*
* @typedef {function(boolean, string,
* number):
* (string|number)} StringOrNumber2
*/

/**
* Makes use of a function type with a long signature.
* @param {StringOrNumber2} func The function.
* @param {boolean} bool The condition.
* @param {string} str The string.
* @param {number} num The number.
* @returns {string|number} The return.
*/
function use2(func, bool, str, num) {
return func(bool, str, num)
}

==== tests/cases/conformance/jsdoc/mod5.js (0 errors) ====
/**
* @typedef {{
* num:
* number,
* str:
* string,
* boo:
* boolean
* }} Type5
*/

/**
* Makes use of a type with a multiline type expression.
* @param {Type5} obj The object.
* @returns {string|number} The return.
*/
function check5(obj) {
return obj.boo ? obj.num : obj.str;
}

==== tests/cases/conformance/jsdoc/mod6.js (0 errors) ====
/**
* @typedef {{
* foo:
* *,
* bar:
* *
* }} Type6
*/

/**
* Makes use of a type with a multiline type expression.
* @param {Type6} obj The object.
* @returns {*} The return.
*/
function check6(obj) {
return obj.foo;
}


==== tests/cases/conformance/jsdoc/mod7.js (2 errors) ====
/**
Multiline type expressions in comments without leading * are not supported.
@typedef {{
foo:
*,
~
!!! error TS1110: Type expected.
bar:
*
}} Type7
~
!!! error TS1110: Type expected.
*/

Loading

0 comments on commit 262ea5b

Please sign in to comment.