Skip to content

Commit ca8d9e4

Browse files
authored
fix(39836): allow type declaration/unknown type in catch arguments in JavaScript files (#42392)
1 parent 38fdce9 commit ca8d9e4

15 files changed

+619
-12
lines changed

src/compiler/checker.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -8847,9 +8847,11 @@ namespace ts {
88478847
Debug.assertIsDefined(symbol.valueDeclaration);
88488848
const declaration = symbol.valueDeclaration;
88498849
if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) {
8850-
const decl = declaration as VariableDeclaration;
8851-
if (!decl.type) return anyType;
8852-
const type = getTypeOfNode(decl.type);
8850+
const typeNode = getEffectiveTypeAnnotationNode(declaration);
8851+
if (typeNode === undefined) {
8852+
return anyType;
8853+
}
8854+
const type = getTypeOfNode(typeNode);
88538855
// an errorType will make `checkTryStatement` issue an error
88548856
return isTypeAny(type) || type === unknownType ? type : errorType;
88558857
}
@@ -36044,10 +36046,11 @@ namespace ts {
3604436046
// Grammar checking
3604536047
if (catchClause.variableDeclaration) {
3604636048
const declaration = catchClause.variableDeclaration;
36047-
if (declaration.type) {
36049+
const typeNode = getEffectiveTypeAnnotationNode(getRootDeclaration(declaration));
36050+
if (typeNode) {
3604836051
const type = getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ false);
3604936052
if (type && !(type.flags & TypeFlags.AnyOrUnknown)) {
36050-
grammarErrorOnFirstToken(declaration.type, Diagnostics.Catch_clause_variable_type_annotation_must_be_any_or_unknown_if_specified);
36053+
grammarErrorOnFirstToken(typeNode, Diagnostics.Catch_clause_variable_type_annotation_must_be_any_or_unknown_if_specified);
3605136054
}
3605236055
}
3605336056
else if (declaration.initializer) {

src/compiler/parser.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -6279,6 +6279,7 @@ namespace ts {
62796279

62806280
function parseVariableDeclaration(allowExclamation?: boolean): VariableDeclaration {
62816281
const pos = getNodePos();
6282+
const hasJSDoc = hasPrecedingJSDocComment();
62826283
const name = parseIdentifierOrPattern(Diagnostics.Private_identifiers_are_not_allowed_in_variable_declarations);
62836284
let exclamationToken: ExclamationToken | undefined;
62846285
if (allowExclamation && name.kind === SyntaxKind.Identifier &&
@@ -6288,7 +6289,7 @@ namespace ts {
62886289
const type = parseTypeAnnotation();
62896290
const initializer = isInOrOfKeyword(token()) ? undefined : parseInitializer();
62906291
const node = factory.createVariableDeclaration(name, exclamationToken, type, initializer);
6291-
return finishNode(node, pos);
6292+
return withJSDoc(finishNode(node, pos), hasJSDoc);
62926293
}
62936294

62946295
function parseVariableDeclarationList(inForStatementInitializer: boolean): VariableDeclarationList {

src/compiler/types.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,7 @@ namespace ts {
874874
| FunctionDeclaration
875875
| ConstructorDeclaration
876876
| MethodDeclaration
877+
| VariableDeclaration
877878
| PropertyDeclaration
878879
| AccessorDeclaration
879880
| ClassLikeDeclaration
@@ -1237,7 +1238,7 @@ namespace ts {
12371238

12381239
export type BindingName = Identifier | BindingPattern;
12391240

1240-
export interface VariableDeclaration extends NamedDeclaration {
1241+
export interface VariableDeclaration extends NamedDeclaration, JSDocContainer {
12411242
readonly kind: SyntaxKind.VariableDeclaration;
12421243
readonly parent: VariableDeclarationList | CatchClause;
12431244
readonly name: BindingName; // Declared variable name

src/compiler/utilities.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1204,7 +1204,8 @@ namespace ts {
12041204
node.kind === SyntaxKind.TypeParameter ||
12051205
node.kind === SyntaxKind.FunctionExpression ||
12061206
node.kind === SyntaxKind.ArrowFunction ||
1207-
node.kind === SyntaxKind.ParenthesizedExpression) ?
1207+
node.kind === SyntaxKind.ParenthesizedExpression ||
1208+
node.kind === SyntaxKind.VariableDeclaration) ?
12081209
concatenate(getTrailingCommentRanges(text, node.pos), getLeadingCommentRanges(text, node.pos)) :
12091210
getLeadingCommentRanges(text, node.pos);
12101211
// True if the comment starts with '/**' but not if it is '/**/'

tests/baselines/reference/api/tsserverlibrary.d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,7 @@ declare namespace ts {
558558
}
559559
export interface JSDocContainer {
560560
}
561-
export type HasJSDoc = ParameterDeclaration | CallSignatureDeclaration | ConstructSignatureDeclaration | MethodSignature | PropertySignature | ArrowFunction | ParenthesizedExpression | SpreadAssignment | ShorthandPropertyAssignment | PropertyAssignment | FunctionExpression | LabeledStatement | ExpressionStatement | VariableStatement | FunctionDeclaration | ConstructorDeclaration | MethodDeclaration | PropertyDeclaration | AccessorDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumMember | EnumDeclaration | ModuleDeclaration | ImportEqualsDeclaration | ImportDeclaration | NamespaceExportDeclaration | ExportAssignment | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | ExportDeclaration | NamedTupleMember | EndOfFileToken;
561+
export type HasJSDoc = ParameterDeclaration | CallSignatureDeclaration | ConstructSignatureDeclaration | MethodSignature | PropertySignature | ArrowFunction | ParenthesizedExpression | SpreadAssignment | ShorthandPropertyAssignment | PropertyAssignment | FunctionExpression | LabeledStatement | ExpressionStatement | VariableStatement | FunctionDeclaration | ConstructorDeclaration | MethodDeclaration | VariableDeclaration | PropertyDeclaration | AccessorDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumMember | EnumDeclaration | ModuleDeclaration | ImportEqualsDeclaration | ImportDeclaration | NamespaceExportDeclaration | ExportAssignment | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | ExportDeclaration | NamedTupleMember | EndOfFileToken;
562562
export type HasType = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertySignature | PropertyDeclaration | TypePredicateNode | ParenthesizedTypeNode | TypeOperatorNode | MappedTypeNode | AssertionExpression | TypeAliasDeclaration | JSDocTypeExpression | JSDocNonNullableType | JSDocNullableType | JSDocOptionalType | JSDocVariadicType;
563563
export type HasTypeArguments = CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement | JsxSelfClosingElement;
564564
export type HasInitializer = HasExpressionInitializer | ForStatement | ForInStatement | ForOfStatement | JsxAttribute;
@@ -687,7 +687,7 @@ declare namespace ts {
687687
readonly kind: SyntaxKind.ConstructSignature;
688688
}
689689
export type BindingName = Identifier | BindingPattern;
690-
export interface VariableDeclaration extends NamedDeclaration {
690+
export interface VariableDeclaration extends NamedDeclaration, JSDocContainer {
691691
readonly kind: SyntaxKind.VariableDeclaration;
692692
readonly parent: VariableDeclarationList | CatchClause;
693693
readonly name: BindingName;

tests/baselines/reference/api/typescript.d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,7 @@ declare namespace ts {
558558
}
559559
export interface JSDocContainer {
560560
}
561-
export type HasJSDoc = ParameterDeclaration | CallSignatureDeclaration | ConstructSignatureDeclaration | MethodSignature | PropertySignature | ArrowFunction | ParenthesizedExpression | SpreadAssignment | ShorthandPropertyAssignment | PropertyAssignment | FunctionExpression | LabeledStatement | ExpressionStatement | VariableStatement | FunctionDeclaration | ConstructorDeclaration | MethodDeclaration | PropertyDeclaration | AccessorDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumMember | EnumDeclaration | ModuleDeclaration | ImportEqualsDeclaration | ImportDeclaration | NamespaceExportDeclaration | ExportAssignment | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | ExportDeclaration | NamedTupleMember | EndOfFileToken;
561+
export type HasJSDoc = ParameterDeclaration | CallSignatureDeclaration | ConstructSignatureDeclaration | MethodSignature | PropertySignature | ArrowFunction | ParenthesizedExpression | SpreadAssignment | ShorthandPropertyAssignment | PropertyAssignment | FunctionExpression | LabeledStatement | ExpressionStatement | VariableStatement | FunctionDeclaration | ConstructorDeclaration | MethodDeclaration | VariableDeclaration | PropertyDeclaration | AccessorDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | EnumMember | EnumDeclaration | ModuleDeclaration | ImportEqualsDeclaration | ImportDeclaration | NamespaceExportDeclaration | ExportAssignment | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | JSDocFunctionType | ExportDeclaration | NamedTupleMember | EndOfFileToken;
562562
export type HasType = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertySignature | PropertyDeclaration | TypePredicateNode | ParenthesizedTypeNode | TypeOperatorNode | MappedTypeNode | AssertionExpression | TypeAliasDeclaration | JSDocTypeExpression | JSDocNonNullableType | JSDocNullableType | JSDocOptionalType | JSDocVariadicType;
563563
export type HasTypeArguments = CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement | JsxSelfClosingElement;
564564
export type HasInitializer = HasExpressionInitializer | ForStatement | ForInStatement | ForOfStatement | JsxAttribute;
@@ -687,7 +687,7 @@ declare namespace ts {
687687
readonly kind: SyntaxKind.ConstructSignature;
688688
}
689689
export type BindingName = Identifier | BindingPattern;
690-
export interface VariableDeclaration extends NamedDeclaration {
690+
export interface VariableDeclaration extends NamedDeclaration, JSDocContainer {
691691
readonly kind: SyntaxKind.VariableDeclaration;
692692
readonly parent: VariableDeclarationList | CatchClause;
693693
readonly name: BindingName;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
tests/cases/conformance/jsdoc/foo.js(20,54): error TS2339: Property 'foo' does not exist on type 'unknown'.
2+
tests/cases/conformance/jsdoc/foo.js(21,54): error TS2339: Property 'foo' does not exist on type 'unknown'.
3+
tests/cases/conformance/jsdoc/foo.js(22,31): error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
4+
tests/cases/conformance/jsdoc/foo.js(23,31): error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
5+
tests/cases/conformance/jsdoc/foo.js(35,7): error TS2492: Cannot redeclare identifier 'err' in catch clause.
6+
tests/cases/conformance/jsdoc/foo.js(48,31): error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
7+
tests/cases/conformance/jsdoc/foo.js(49,31): error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
8+
9+
10+
==== tests/cases/conformance/jsdoc/foo.js (7 errors) ====
11+
/**
12+
* @typedef {any} Any
13+
*/
14+
15+
/**
16+
* @typedef {unknown} Unknown
17+
*/
18+
19+
function fn() {
20+
try { } catch (x) { } // should be OK
21+
try { } catch (/** @type {any} */ err) { } // should be OK
22+
try { } catch (/** @type {Any} */ err) { } // should be OK
23+
try { } catch (/** @type {unknown} */ err) { } // should be OK
24+
try { } catch (/** @type {Unknown} */ err) { } // should be OK
25+
try { } catch (err) { err.foo; } // should be OK
26+
try { } catch (/** @type {any} */ err) { err.foo; } // should be OK
27+
try { } catch (/** @type {Any} */ err) { err.foo; } // should be OK
28+
try { } catch (/** @type {unknown} */ err) { console.log(err); } // should be OK
29+
try { } catch (/** @type {Unknown} */ err) { console.log(err); } // should be OK
30+
try { } catch (/** @type {unknown} */ err) { err.foo; } // error in the body
31+
~~~
32+
!!! error TS2339: Property 'foo' does not exist on type 'unknown'.
33+
try { } catch (/** @type {Unknown} */ err) { err.foo; } // error in the body
34+
~~~
35+
!!! error TS2339: Property 'foo' does not exist on type 'unknown'.
36+
try { } catch (/** @type {Error} */ err) { } // error in the type
37+
~~~~~
38+
!!! error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
39+
try { } catch (/** @type {object} */ err) { } // error in the type
40+
~~~~~~
41+
!!! error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
42+
43+
try { console.log(); }
44+
// @ts-ignore
45+
catch (/** @type {number} */ err) { // e should not be a `number`
46+
console.log(err.toLowerCase());
47+
}
48+
49+
// minor bug: shows that the `catch` argument is skipped when checking scope
50+
try { }
51+
catch (err) {
52+
/** @type {string} */
53+
let err;
54+
~~~
55+
!!! error TS2492: Cannot redeclare identifier 'err' in catch clause.
56+
}
57+
try { }
58+
catch (err) {
59+
/** @type {boolean} */
60+
var err;
61+
}
62+
63+
try { } catch ({ x }) { } // should be OK
64+
try { } catch (/** @type {any} */ { x }) { x.foo; } // should be OK
65+
try { } catch (/** @type {Any} */ { x }) { x.foo;} // should be OK
66+
try { } catch (/** @type {unknown} */ { x }) { console.log(x); } // should be OK
67+
try { } catch (/** @type {Unknown} */ { x }) { console.log(x); } // should be OK
68+
try { } catch (/** @type {Error} */ { x }) { } // error in the type
69+
~~~~~
70+
!!! error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
71+
try { } catch (/** @type {object} */ { x }) { } // error in the type
72+
~~~~~~
73+
!!! error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
74+
}
75+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//// [foo.js]
2+
/**
3+
* @typedef {any} Any
4+
*/
5+
6+
/**
7+
* @typedef {unknown} Unknown
8+
*/
9+
10+
function fn() {
11+
try { } catch (x) { } // should be OK
12+
try { } catch (/** @type {any} */ err) { } // should be OK
13+
try { } catch (/** @type {Any} */ err) { } // should be OK
14+
try { } catch (/** @type {unknown} */ err) { } // should be OK
15+
try { } catch (/** @type {Unknown} */ err) { } // should be OK
16+
try { } catch (err) { err.foo; } // should be OK
17+
try { } catch (/** @type {any} */ err) { err.foo; } // should be OK
18+
try { } catch (/** @type {Any} */ err) { err.foo; } // should be OK
19+
try { } catch (/** @type {unknown} */ err) { console.log(err); } // should be OK
20+
try { } catch (/** @type {Unknown} */ err) { console.log(err); } // should be OK
21+
try { } catch (/** @type {unknown} */ err) { err.foo; } // error in the body
22+
try { } catch (/** @type {Unknown} */ err) { err.foo; } // error in the body
23+
try { } catch (/** @type {Error} */ err) { } // error in the type
24+
try { } catch (/** @type {object} */ err) { } // error in the type
25+
26+
try { console.log(); }
27+
// @ts-ignore
28+
catch (/** @type {number} */ err) { // e should not be a `number`
29+
console.log(err.toLowerCase());
30+
}
31+
32+
// minor bug: shows that the `catch` argument is skipped when checking scope
33+
try { }
34+
catch (err) {
35+
/** @type {string} */
36+
let err;
37+
}
38+
try { }
39+
catch (err) {
40+
/** @type {boolean} */
41+
var err;
42+
}
43+
44+
try { } catch ({ x }) { } // should be OK
45+
try { } catch (/** @type {any} */ { x }) { x.foo; } // should be OK
46+
try { } catch (/** @type {Any} */ { x }) { x.foo;} // should be OK
47+
try { } catch (/** @type {unknown} */ { x }) { console.log(x); } // should be OK
48+
try { } catch (/** @type {Unknown} */ { x }) { console.log(x); } // should be OK
49+
try { } catch (/** @type {Error} */ { x }) { } // error in the type
50+
try { } catch (/** @type {object} */ { x }) { } // error in the type
51+
}
52+
53+
54+
//// [foo.js]
55+
/**
56+
* @typedef {any} Any
57+
*/
58+
/**
59+
* @typedef {unknown} Unknown
60+
*/
61+
function fn() {
62+
try { }
63+
catch (x) { } // should be OK
64+
try { }
65+
catch ( /** @type {any} */err) { } // should be OK
66+
try { }
67+
catch ( /** @type {Any} */err) { } // should be OK
68+
try { }
69+
catch ( /** @type {unknown} */err) { } // should be OK
70+
try { }
71+
catch ( /** @type {Unknown} */err) { } // should be OK
72+
try { }
73+
catch (err) {
74+
err.foo;
75+
} // should be OK
76+
try { }
77+
catch ( /** @type {any} */err) {
78+
err.foo;
79+
} // should be OK
80+
try { }
81+
catch ( /** @type {Any} */err) {
82+
err.foo;
83+
} // should be OK
84+
try { }
85+
catch ( /** @type {unknown} */err) {
86+
console.log(err);
87+
} // should be OK
88+
try { }
89+
catch ( /** @type {Unknown} */err) {
90+
console.log(err);
91+
} // should be OK
92+
try { }
93+
catch ( /** @type {unknown} */err) {
94+
err.foo;
95+
} // error in the body
96+
try { }
97+
catch ( /** @type {Unknown} */err) {
98+
err.foo;
99+
} // error in the body
100+
try { }
101+
catch ( /** @type {Error} */err) { } // error in the type
102+
try { }
103+
catch ( /** @type {object} */err) { } // error in the type
104+
try {
105+
console.log();
106+
}
107+
// @ts-ignore
108+
catch ( /** @type {number} */err) { // e should not be a `number`
109+
console.log(err.toLowerCase());
110+
}
111+
// minor bug: shows that the `catch` argument is skipped when checking scope
112+
try { }
113+
catch (err) {
114+
/** @type {string} */
115+
let err;
116+
}
117+
try { }
118+
catch (err) {
119+
/** @type {boolean} */
120+
var err;
121+
}
122+
try { }
123+
catch ({ x }) { } // should be OK
124+
try { }
125+
catch ( /** @type {any} */{ x }) {
126+
x.foo;
127+
} // should be OK
128+
try { }
129+
catch ( /** @type {Any} */{ x }) {
130+
x.foo;
131+
} // should be OK
132+
try { }
133+
catch ( /** @type {unknown} */{ x }) {
134+
console.log(x);
135+
} // should be OK
136+
try { }
137+
catch ( /** @type {Unknown} */{ x }) {
138+
console.log(x);
139+
} // should be OK
140+
try { }
141+
catch ( /** @type {Error} */{ x }) { } // error in the type
142+
try { }
143+
catch ( /** @type {object} */{ x }) { } // error in the type
144+
}

0 commit comments

Comments
 (0)