diff --git a/linter-4.2/src/TypeScriptLinter.ts b/linter-4.2/src/TypeScriptLinter.ts index d7622bcc47ac8a15aca4f1afd047b98a4d56b65f..bb2d8634ac9bf6db69fe1d79d7b4dc7d9019c2c7 100644 --- a/linter-4.2/src/TypeScriptLinter.ts +++ b/linter-4.2/src/TypeScriptLinter.ts @@ -836,7 +836,7 @@ export class TypeScriptLinter { if (ts.isPropertyDeclaration(node)) { const decorators = node.decorators; this.handleDecorators(decorators); - this.filterOutStrictDiagnostics(decorators, TsUtils.NON_INITIALIZABLE_PROPERTY_DECORATORS, + this.filterOutDecoratorsDiagnostics(decorators, TsUtils.NON_INITIALIZABLE_PROPERTY_DECORATORS, {begin: propName.getStart(), end: propName.getStart()}, TsUtils.PROPERTY_HAS_NO_INITIALIZER_ERROR_CODE); this.handleDeclarationInferredType(node); @@ -844,7 +844,7 @@ export class TypeScriptLinter { } } - private filterOutStrictDiagnostics( + private filterOutDecoratorsDiagnostics( decorators: readonly ts.Decorator[] | undefined, expectedDecorators: readonly string[], range: {begin: number, end: number}, @@ -897,6 +897,34 @@ export class TypeScriptLinter { } } + private filterStrictDiagnostics(range: { begin: number, end: number }, code: number, + chainCheck: (n: string | ts.DiagnosticMessageChain) => boolean): boolean { + if (!this.tscStrictDiagnostics || !this.sourceFile) { + return false; + } + let file = path.normalize(this.sourceFile.fileName); + let tscDiagnostics = this.tscStrictDiagnostics.get(file) + if (!tscDiagnostics) { + return false; + } + + const checkDiagnostic = (val: ts.Diagnostic) => { + if (val.code !== code) { + return true; + } + if (val.start === undefined || val.start < range.begin || val.start > range.end) { + return true; + } + return chainCheck(val.messageText); + }; + + if (tscDiagnostics.every(checkDiagnostic)) { + return false; + } + this.tscStrictDiagnostics.set(file, tscDiagnostics.filter(checkDiagnostic)); + return true; + } + private handleFunctionExpression(node: ts.Node) { const funcExpr = node as ts.FunctionExpression; const isGenerator = funcExpr.asteriskToken !== undefined; @@ -1511,7 +1539,7 @@ export class TypeScriptLinter { this.handleDecorators(tsMethodDecl.decorators); - this.filterOutStrictDiagnostics(tsMethodDecl.decorators, TsUtils.NON_RETURN_FUNCTION_DECORATORS, + this.filterOutDecoratorsDiagnostics(tsMethodDecl.decorators, TsUtils.NON_RETURN_FUNCTION_DECORATORS, {begin: tsMethodDecl.parameters.end, end: tsMethodDecl.body?.getStart() ?? tsMethodDecl.parameters.end}, TsUtils.FUNCTION_HAS_NO_RETURN_ERROR_CODE); } @@ -1827,6 +1855,7 @@ export class TypeScriptLinter { this.handleGenericCallWithNoTypeArgs(tsCallExpr); this.handleStructIdentAndUndefinedInArgs(tsCallExpr); this.handleStdlibAPICall(tsCallExpr); + this.handleLibraryTypeCall(tsCallExpr); } private handleImportCall(tsCallExpr: ts.CallExpression) { @@ -1997,6 +2026,27 @@ export class TypeScriptLinter { } } + private handleLibraryTypeCall(callExpr: ts.CallExpression) { + // Permit for restricted callable types? + // if (!this.tsUtils.isLibraryType(this.tsTypeChecker.getTypeAtLocation(callExpr.expression))) { + // return; + // } + const TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE = 2322; + const TYPE_UNKNOWN_ANY_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE = /^Type 'unknown' is not assignable to type '.*'.$/; + const ARGUMENT_OF_TYPE_0_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_ERROR_CODE = 2345; + + const chainCheck = (n: ts.DiagnosticMessageChain): boolean => { + if (n.code == TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE && + n.messageText.match(TYPE_UNKNOWN_ANY_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE)) { + return false; + } + return n.next == undefined ? true : chainCheck(n.next[0]); + }; + this.filterStrictDiagnostics({ begin: callExpr.pos, end: callExpr.end }, + ARGUMENT_OF_TYPE_0_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_ERROR_CODE, + (msg) => (typeof msg == 'string' ? true : chainCheck(msg))); + } + private handleNewExpression(node: ts.Node) { let tsNewExpr = node as ts.NewExpression; this.handleGenericCallWithNoTypeArgs(tsNewExpr); diff --git a/linter-4.2/test/1unknown_params.ts b/linter-4.2/test/1unknown_params.ts new file mode 100644 index 0000000000000000000000000000000000000000..0a2b1d6124663f4e847e494cad03e9ac294dbc53 --- /dev/null +++ b/linter-4.2/test/1unknown_params.ts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { applyToUnknown, fooExecute } from "./dynamic_lib" + +export declare function applyToUnknownETS(fn: (a: unknown) => void); + +function printArg(arg: number): number { + console.log(arg); + return arg; +} + +function main(): void { + applyToUnknown((x: number) => { }); + + applyToUnknownETS((x: number) => { }); + + fooExecute(printArg, 1).then((value: number) => { + console.log(value); + }); + fooExecute(printArg, 1).then((value: Object[]) => { + console.log(value); + }); +} diff --git a/linter-4.2/test/1unknown_params.ts.autofix.skip b/linter-4.2/test/1unknown_params.ts.autofix.skip new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/linter-4.2/test/1unknown_params.ts.relax.json b/linter-4.2/test/1unknown_params.ts.relax.json new file mode 100644 index 0000000000000000000000000000000000000000..8f486a9660829a684bfcdbbdb7a60f0ff3e5c1d3 --- /dev/null +++ b/linter-4.2/test/1unknown_params.ts.relax.json @@ -0,0 +1,25 @@ +{ + "copyright": [ + "Copyright (c) 2023-2023 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "nodes": [ + { + "line": 18, + "column": 51, + "problem": "UnknownType", + "suggest": "", + "rule": "Use explicit types instead of \"any\", \"unknown\" (arkts-no-any-unknown)" + } + ] +} \ No newline at end of file diff --git a/linter-4.2/test/1unknown_params.ts.strict.json b/linter-4.2/test/1unknown_params.ts.strict.json new file mode 100644 index 0000000000000000000000000000000000000000..8f486a9660829a684bfcdbbdb7a60f0ff3e5c1d3 --- /dev/null +++ b/linter-4.2/test/1unknown_params.ts.strict.json @@ -0,0 +1,25 @@ +{ + "copyright": [ + "Copyright (c) 2023-2023 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "nodes": [ + { + "line": 18, + "column": 51, + "problem": "UnknownType", + "suggest": "", + "rule": "Use explicit types instead of \"any\", \"unknown\" (arkts-no-any-unknown)" + } + ] +} \ No newline at end of file diff --git a/linter/src/TypeScriptLinter.ts b/linter/src/TypeScriptLinter.ts index ee117b90f1fba76ec38837204ba95603690a1b0b..e55996ed96347034f83154fba097bca972286a1c 100644 --- a/linter/src/TypeScriptLinter.ts +++ b/linter/src/TypeScriptLinter.ts @@ -665,14 +665,14 @@ export class TypeScriptLinter { if (ts.isPropertyDeclaration(node)) { const decorators = ts.getDecorators(node); this.handleDecorators(decorators); - this.filterOutStrictDiagnostics(decorators, NON_INITIALIZABLE_PROPERTY_DECORATORS, + this.filterOutDecoratorsDiagnostics(decorators, NON_INITIALIZABLE_PROPERTY_DECORATORS, {begin: propName.getStart(), end: propName.getStart()}, PROPERTY_HAS_NO_INITIALIZER_ERROR_CODE); this.handleDeclarationInferredType(node); this.handleDefiniteAssignmentAssertion(node); } } - private filterOutStrictDiagnostics( + private filterOutDecoratorsDiagnostics( decorators: readonly ts.Decorator[] | undefined, expectedDecorators: readonly string[], range: {begin: number, end: number}, @@ -719,6 +719,34 @@ export class TypeScriptLinter { } } + private filterStrictDiagnostics(range: { begin: number, end: number }, code: number, + chainCheck: (n: string | ts.DiagnosticMessageChain) => boolean): boolean { + if (!this.tscStrictDiagnostics || !this.sourceFile) { + return false; + } + let file = path.normalize(this.sourceFile.fileName); + let tscDiagnostics = this.tscStrictDiagnostics.get(file) + if (!tscDiagnostics) { + return false; + } + + const checkDiagnostic = (val: ts.Diagnostic) => { + if (val.code !== code) { + return true; + } + if (val.start === undefined || val.start < range.begin || val.start > range.end) { + return true; + } + return chainCheck(val.messageText); + }; + + if (tscDiagnostics.every(checkDiagnostic)) { + return false; + } + this.tscStrictDiagnostics.set(file, tscDiagnostics.filter(checkDiagnostic)); + return true; + } + private handleFunctionExpression(node: ts.Node) { const funcExpr = node as ts.FunctionExpression; const isGenerator = funcExpr.asteriskToken !== undefined; @@ -1186,7 +1214,7 @@ export class TypeScriptLinter { this.handleDecorators(ts.getDecorators(tsMethodDecl)); - this.filterOutStrictDiagnostics(ts.getDecorators(tsMethodDecl), NON_RETURN_FUNCTION_DECORATORS, + this.filterOutDecoratorsDiagnostics(ts.getDecorators(tsMethodDecl), NON_RETURN_FUNCTION_DECORATORS, {begin: tsMethodDecl.parameters.end, end: tsMethodDecl.body?.getStart() ?? tsMethodDecl.parameters.end}, FUNCTION_HAS_NO_RETURN_ERROR_CODE); } @@ -1407,6 +1435,7 @@ export class TypeScriptLinter { this.handleGenericCallWithNoTypeArgs(tsCallExpr); this.handleStructIdentAndUndefinedInArgs(tsCallExpr); this.handleStdlibAPICall(tsCallExpr); + this.handleLibraryTypeCall(tsCallExpr); } private handleImportCall(tsCallExpr: ts.CallExpression) { @@ -1541,6 +1570,27 @@ export class TypeScriptLinter { } } + private handleLibraryTypeCall(callExpr: ts.CallExpression) { + // Permit for restricted callable types? + // if (!this.tsUtils.isLibraryType(this.tsTypeChecker.getTypeAtLocation(callExpr.expression))) { + // return; + // } + const TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE = 2322; + const TYPE_UNKNOWN_ANY_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE = /^Type 'unknown' is not assignable to type '.*'.$/; + const ARGUMENT_OF_TYPE_0_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_ERROR_CODE = 2345; + + const chainCheck = (n: ts.DiagnosticMessageChain): boolean => { + if (n.code == TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE && + n.messageText.match(TYPE_UNKNOWN_ANY_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE)) { + return false; + } + return n.next == undefined ? true : chainCheck(n.next[0]); + }; + this.filterStrictDiagnostics({ begin: callExpr.pos, end: callExpr.end }, + ARGUMENT_OF_TYPE_0_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_ERROR_CODE, + (msg) => (typeof msg == 'string' ? true : chainCheck(msg))); + } + private handleNewExpression(node: ts.Node) { let tsNewExpr = node as ts.NewExpression; this.handleGenericCallWithNoTypeArgs(tsNewExpr); diff --git a/linter/test/1unknown_params.ts b/linter/test/1unknown_params.ts new file mode 100644 index 0000000000000000000000000000000000000000..0a2b1d6124663f4e847e494cad03e9ac294dbc53 --- /dev/null +++ b/linter/test/1unknown_params.ts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { applyToUnknown, fooExecute } from "./dynamic_lib" + +export declare function applyToUnknownETS(fn: (a: unknown) => void); + +function printArg(arg: number): number { + console.log(arg); + return arg; +} + +function main(): void { + applyToUnknown((x: number) => { }); + + applyToUnknownETS((x: number) => { }); + + fooExecute(printArg, 1).then((value: number) => { + console.log(value); + }); + fooExecute(printArg, 1).then((value: Object[]) => { + console.log(value); + }); +} diff --git a/linter/test/1unknown_params.ts.autofix.skip b/linter/test/1unknown_params.ts.autofix.skip new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/linter/test/1unknown_params.ts.relax.json b/linter/test/1unknown_params.ts.relax.json new file mode 100644 index 0000000000000000000000000000000000000000..8f486a9660829a684bfcdbbdb7a60f0ff3e5c1d3 --- /dev/null +++ b/linter/test/1unknown_params.ts.relax.json @@ -0,0 +1,25 @@ +{ + "copyright": [ + "Copyright (c) 2023-2023 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "nodes": [ + { + "line": 18, + "column": 51, + "problem": "UnknownType", + "suggest": "", + "rule": "Use explicit types instead of \"any\", \"unknown\" (arkts-no-any-unknown)" + } + ] +} \ No newline at end of file diff --git a/linter/test/1unknown_params.ts.strict.json b/linter/test/1unknown_params.ts.strict.json new file mode 100644 index 0000000000000000000000000000000000000000..8f486a9660829a684bfcdbbdb7a60f0ff3e5c1d3 --- /dev/null +++ b/linter/test/1unknown_params.ts.strict.json @@ -0,0 +1,25 @@ +{ + "copyright": [ + "Copyright (c) 2023-2023 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "nodes": [ + { + "line": 18, + "column": 51, + "problem": "UnknownType", + "suggest": "", + "rule": "Use explicit types instead of \"any\", \"unknown\" (arkts-no-any-unknown)" + } + ] +} \ No newline at end of file diff --git a/linter/test/dynamic_lib.d.ts b/linter/test/dynamic_lib.d.ts index b5eecaeef7700f080fd00a57fb8f705ed00e95cc..e32ce8637e99cf49958b756fbfffbf49e9734968 100644 --- a/linter/test/dynamic_lib.d.ts +++ b/linter/test/dynamic_lib.d.ts @@ -11,3 +11,6 @@ export declare interface I2 { f2?: Array; f3?: any; } + +export declare function applyToUnknown(fn: (a: unknown) => void); +export declare function fooExecute(func: Function, ...args: unknown[]): Promise;