Skip asterisks after newline when parsing JSDoc types (#26528)

* Skip asterisks after newline when parsing JSDoc types

* Single boolean expression

* Test for parsing and printing multiline function signatures with *
This commit is contained in:
Tim Schaub
2018-09-04 16:41:08 -06:00
committed by Nathan Shively-Sanders
parent 64ac5a53f4
commit 262ea5b06e
8 changed files with 641 additions and 12 deletions
+2
View File
@@ -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;
+15
View File
@@ -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.
@@ -824,6 +826,8 @@ namespace ts {
let tokenValue!: string;
let tokenFlags: TokenFlags;
let inJSDocType = 0;
setText(text, start, length);
return {
@@ -854,6 +858,7 @@ namespace ts {
setLanguageVariant,
setOnError,
setTextPos,
setInJSDocType,
tryScan,
lookAhead,
scanRange,
@@ -1350,6 +1355,7 @@ namespace ts {
function scan(): SyntaxKind {
startPos = pos;
tokenFlags = 0;
let asteriskSeen = false;
while (true) {
tokenPos = pos;
if (pos >= end) {
@@ -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) {
@@ -2078,5 +2089,9 @@ namespace ts {
tokenValue = undefined!;
tokenFlags = 0;
}
function setInJSDocType(inType: boolean) {
inJSDocType += inType ? 1 : -1;
}
}
}
+12 -1
View File
@@ -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 {
@@ -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.
*/
@@ -1,13 +1,13 @@
=== tests/cases/conformance/jsdoc/mod1.js ===
/**
* @typedef {function(string): boolean}
* MyType
* Type1
*/
/**
* Tries to use a type whose name is on a different
* line than the typedef tag.
* @param {MyType} func The function to call.
* @param {Type1} func The function to call.
* @param {string} arg The argument to call it with.
* @returns {boolean} The return.
*/
@@ -21,3 +21,165 @@ function callIt(func, arg) {
>arg : Symbol(arg, Decl(mod1.js, 12, 21))
}
=== tests/cases/conformance/jsdoc/mod2.js ===
/**
* @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) {
>check : Symbol(check, Decl(mod2.js, 0, 0))
>obj : Symbol(obj, Decl(mod2.js, 13, 15))
return obj.boo ? obj.num : obj.str;
>obj.boo : Symbol(boo, Decl(mod2.js, 3, 17))
>obj : Symbol(obj, Decl(mod2.js, 13, 15))
>boo : Symbol(boo, Decl(mod2.js, 3, 17))
>obj.num : Symbol(num, Decl(mod2.js, 1, 14))
>obj : Symbol(obj, Decl(mod2.js, 13, 15))
>num : Symbol(num, Decl(mod2.js, 1, 14))
>obj.str : Symbol(str, Decl(mod2.js, 2, 17))
>obj : Symbol(obj, Decl(mod2.js, 13, 15))
>str : Symbol(str, Decl(mod2.js, 2, 17))
}
=== tests/cases/conformance/jsdoc/mod3.js ===
/**
* 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) {
>use1 : Symbol(use1, Decl(mod3.js, 0, 0))
>func : Symbol(func, Decl(mod3.js, 15, 14))
>bool : Symbol(bool, Decl(mod3.js, 15, 19))
>str : Symbol(str, Decl(mod3.js, 15, 25))
>num : Symbol(num, Decl(mod3.js, 15, 30))
return func(bool, str, num)
>func : Symbol(func, Decl(mod3.js, 15, 14))
>bool : Symbol(bool, Decl(mod3.js, 15, 19))
>str : Symbol(str, Decl(mod3.js, 15, 25))
>num : Symbol(num, Decl(mod3.js, 15, 30))
}
=== tests/cases/conformance/jsdoc/mod4.js ===
/**
* 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) {
>use2 : Symbol(use2, Decl(mod4.js, 0, 0))
>func : Symbol(func, Decl(mod4.js, 16, 14))
>bool : Symbol(bool, Decl(mod4.js, 16, 19))
>str : Symbol(str, Decl(mod4.js, 16, 25))
>num : Symbol(num, Decl(mod4.js, 16, 30))
return func(bool, str, num)
>func : Symbol(func, Decl(mod4.js, 16, 14))
>bool : Symbol(bool, Decl(mod4.js, 16, 19))
>str : Symbol(str, Decl(mod4.js, 16, 25))
>num : Symbol(num, Decl(mod4.js, 16, 30))
}
=== tests/cases/conformance/jsdoc/mod5.js ===
/**
* @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) {
>check5 : Symbol(check5, Decl(mod5.js, 0, 0))
>obj : Symbol(obj, Decl(mod5.js, 16, 16))
return obj.boo ? obj.num : obj.str;
>obj.boo : Symbol(boo, Decl(mod5.js, 5, 12))
>obj : Symbol(obj, Decl(mod5.js, 16, 16))
>boo : Symbol(boo, Decl(mod5.js, 5, 12))
>obj.num : Symbol(num, Decl(mod5.js, 1, 14))
>obj : Symbol(obj, Decl(mod5.js, 16, 16))
>num : Symbol(num, Decl(mod5.js, 1, 14))
>obj.str : Symbol(str, Decl(mod5.js, 3, 12))
>obj : Symbol(obj, Decl(mod5.js, 16, 16))
>str : Symbol(str, Decl(mod5.js, 3, 12))
}
=== tests/cases/conformance/jsdoc/mod6.js ===
/**
* @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) {
>check6 : Symbol(check6, Decl(mod6.js, 0, 0))
>obj : Symbol(obj, Decl(mod6.js, 14, 16))
return obj.foo;
>obj.foo : Symbol(foo, Decl(mod6.js, 1, 14))
>obj : Symbol(obj, Decl(mod6.js, 14, 16))
>foo : Symbol(foo, Decl(mod6.js, 1, 14))
}
=== tests/cases/conformance/jsdoc/mod7.js ===
/**
No type information for this code. Multiline type expressions in comments without leading * are not supported.
No type information for this code. @typedef {{
No type information for this code. foo:
No type information for this code. *,
No type information for this code. bar:
No type information for this code. *
No type information for this code. }} Type7
No type information for this code. */
No type information for this code.
No type information for this code.
@@ -1,13 +1,13 @@
=== tests/cases/conformance/jsdoc/mod1.js ===
/**
* @typedef {function(string): boolean}
* MyType
* Type1
*/
/**
* Tries to use a type whose name is on a different
* line than the typedef tag.
* @param {MyType} func The function to call.
* @param {Type1} func The function to call.
* @param {string} arg The argument to call it with.
* @returns {boolean} The return.
*/
@@ -22,3 +22,169 @@ function callIt(func, arg) {
>arg : string
}
=== tests/cases/conformance/jsdoc/mod2.js ===
/**
* @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) {
>check : (obj: { num: number; str: string; boo: boolean; }) => string | number
>obj : { num: number; str: string; boo: boolean; }
return obj.boo ? obj.num : obj.str;
>obj.boo ? obj.num : obj.str : string | number
>obj.boo : boolean
>obj : { num: number; str: string; boo: boolean; }
>boo : boolean
>obj.num : number
>obj : { num: number; str: string; boo: boolean; }
>num : number
>obj.str : string
>obj : { num: number; str: string; boo: boolean; }
>str : string
}
=== tests/cases/conformance/jsdoc/mod3.js ===
/**
* 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) {
>use1 : (func: (arg0: boolean, arg1: string, arg2: number) => string | number, bool: boolean, str: string, num: number) => string | number
>func : (arg0: boolean, arg1: string, arg2: number) => string | number
>bool : boolean
>str : string
>num : number
return func(bool, str, num)
>func(bool, str, num) : string | number
>func : (arg0: boolean, arg1: string, arg2: number) => string | number
>bool : boolean
>str : string
>num : number
}
=== tests/cases/conformance/jsdoc/mod4.js ===
/**
* 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) {
>use2 : (func: (arg0: boolean, arg1: string, arg2: number) => string | number, bool: boolean, str: string, num: number) => string | number
>func : (arg0: boolean, arg1: string, arg2: number) => string | number
>bool : boolean
>str : string
>num : number
return func(bool, str, num)
>func(bool, str, num) : string | number
>func : (arg0: boolean, arg1: string, arg2: number) => string | number
>bool : boolean
>str : string
>num : number
}
=== tests/cases/conformance/jsdoc/mod5.js ===
/**
* @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) {
>check5 : (obj: { num: number; str: string; boo: boolean; }) => string | number
>obj : { num: number; str: string; boo: boolean; }
return obj.boo ? obj.num : obj.str;
>obj.boo ? obj.num : obj.str : string | number
>obj.boo : boolean
>obj : { num: number; str: string; boo: boolean; }
>boo : boolean
>obj.num : number
>obj : { num: number; str: string; boo: boolean; }
>num : number
>obj.str : string
>obj : { num: number; str: string; boo: boolean; }
>str : string
}
=== tests/cases/conformance/jsdoc/mod6.js ===
/**
* @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) {
>check6 : (obj: { foo: any; bar: any; }) => any
>obj : { foo: any; bar: any; }
return obj.foo;
>obj.foo : any
>obj : { foo: any; bar: any; }
>foo : any
}
=== tests/cases/conformance/jsdoc/mod7.js ===
/**
No type information for this code. Multiline type expressions in comments without leading * are not supported.
No type information for this code. @typedef {{
No type information for this code. foo:
No type information for this code. *,
No type information for this code. bar:
No type information for this code. *
No type information for this code. }} Type7
No type information for this code. */
No type information for this code.
No type information for this code.
@@ -1,20 +1,138 @@
// @noEmit: true
// @allowJs: true
// @checkJs: true
// @Filename: mod1.js
/**
* @typedef {function(string): boolean}
* MyType
* Type1
*/
/**
* Tries to use a type whose name is on a different
* line than the typedef tag.
* @param {MyType} func The function to call.
* @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);
}
// @Filename: mod2.js
/**
* @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;
}
// @Filename: mod3.js
/**
* 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)
}
// @Filename: mod4.js
/**
* 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)
}
// @Filename: mod5.js
/**
* @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;
}
// @Filename: mod6.js
/**
* @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;
}
// @Filename: mod7.js
/**
Multiline type expressions in comments without leading * are not supported.
@typedef {{
foo:
*,
bar:
*
}} Type7
*/
@@ -1,13 +1,32 @@
///<reference path="fourslash.ts" />
// @allowJs: true
// @Filename: Foo.js
// @Filename: jsDocFunctionSignatures.js
/////**
//// * @param {{ stringProp: string,
//// * numProp: number }} o
//// * @param {{
//// * stringProp: string,
//// * numProp: number,
//// * boolProp: boolean,
//// * anyProp: *,
//// * anotherAnyProp:
//// * *,
//// * functionProp:
//// * function(string,
//// * *):
//// * *
//// * }} o
//// */
////function f1(o) {
//// o/**/;
////}
goTo.marker();
verify.quickInfoIs("(parameter) o: any");
verify.quickInfoIs(`(parameter) o: {
stringProp: string;
numProp: number;
boolProp: boolean;
anyProp: any;
anotherAnyProp: any;
functionProp: (arg0: string, arg1: any) => any;
}`);