diff --git a/linter-4.2/scripts/update-test-results.mjs b/linter-4.2/scripts/update-test-results.mjs index dc579e42453a4416215504af7d0e3133221a4f33..45029c37eb3f2858d3a3848b5eebc2453598d268 100644 --- a/linter-4.2/scripts/update-test-results.mjs +++ b/linter-4.2/scripts/update-test-results.mjs @@ -37,7 +37,7 @@ const RESULTS_DIR = 'results' let testDirs = []; // forces to update all tests regardless of whether there was diff in a test result -const force_update = false; +let force_update = false; for (let arg of process.argv.slice(2)) { if (arg === '--force') @@ -72,33 +72,37 @@ function readTestFile(filePath) { function updateTest(testDir, testFile, mode) { let resultExt = RESULT_EXT[mode]; - let testFileWithExt = testFile + resultExt; + let resultFileWithExt = testFile + resultExt; + let resultFilePath = path.join(testDir, resultFileWithExt); // Do not update autofix result if test is skipped - if (mode === Mode.AUTOFIX && fs.existsSync(path.join(testDir, testFileWithExt + AUTOFIX_SKIP_EXT))) { + if (mode === Mode.AUTOFIX && fs.existsSync(path.join(testDir, testFile + AUTOFIX_SKIP_EXT))) { return; } - // Update test result when 'diff' exists or the 'force' option is enabled. - if (!fs.existsSync(path.join(testDir, RESULTS_DIR, testFileWithExt + DIFF_EXT)) && !force_update) { + // Update test result when: + // - '.diff' exists + // - expected '.json' doesn't exist + // - 'force' option is enabled + if (fs.existsSync(resultFilePath) && !fs.existsSync(path.join(testDir, RESULTS_DIR, resultFileWithExt + DIFF_EXT)) && !force_update) { return; } - let expectedResult = readTestFile(path.join(testDir, testFileWithExt)); + let expectedResult = readTestFile(resultFilePath); const copyright = expectedResult?.copyright ?? DEFAULT_COPYRIGHT; - let actualResult = readTestFile(path.join(testDir, RESULTS_DIR, testFileWithExt)); + let actualResult = readTestFile(path.join(testDir, RESULTS_DIR, resultFileWithExt)); if (!actualResult || !actualResult.nodes) { - console.log(`Failed to update ${testFileWithExt}: couldn't read ACTUAL result file.`); + console.log(`Failed to update ${resultFileWithExt}: couldn't read ACTUAL result file.`); return; } // Write file with actual test results. let newResultJSON = JSON.stringify({ copyright, nodes: actualResult.nodes }, null, 4); - fs.writeFileSync(path.join(testDir, testFileWithExt), newResultJSON); + fs.writeFileSync(resultFilePath, newResultJSON); - console.log(`Updated ${testFileWithExt}`); + console.log(`Updated ${resultFileWithExt}`); } for (let testDir of testDirs) { diff --git a/linter-4.2/src/TypeScriptLinter.ts b/linter-4.2/src/TypeScriptLinter.ts index 9b9719ea27cda575100a71432b8eb1a55db942cf..aa20c83fd08730655c31974a783556f206986727 100644 --- a/linter-4.2/src/TypeScriptLinter.ts +++ b/linter-4.2/src/TypeScriptLinter.ts @@ -1592,9 +1592,7 @@ export class TypeScriptLinter { private handleElementAccessExpression(node: ts.Node) { const tsElementAccessExpr = node as ts.ElementAccessExpression; - const tsElemAccessBaseExprType = this.tsTypeChecker.getTypeAtLocation( - tsElementAccessExpr.expression - ); + const tsElemAccessBaseExprType = this.tsUtils.getTypeOrTypeConstraintAtLocation(tsElementAccessExpr.expression); const tsElemAccessBaseExprTypeNode = this.tsTypeChecker.typeToTypeNode( tsElemAccessBaseExprType, undefined, diff --git a/linter-4.2/src/Utils.ts b/linter-4.2/src/Utils.ts index f8ac72d3676f93041f4b5cdeae8e07ddf1b9a192..196108c67219ad56d950cdec19e9daba4d821c11 100644 --- a/linter-4.2/src/Utils.ts +++ b/linter-4.2/src/Utils.ts @@ -1549,4 +1549,15 @@ export class TsUtils { visitNode(funcExpr); return found; } + + public getTypeOrTypeConstraintAtLocation(expr: ts.Expression): ts.Type { + let type = this.tsTypeChecker.getTypeAtLocation(expr); + if (type.isTypeParameter()) { + let constraint = type.getConstraint(); + if (constraint) { + return constraint; + } + } + return type; + } } diff --git a/linter-4.2/test/property_access_by_index.ts b/linter-4.2/test/property_access_by_index.ts new file mode 100644 index 0000000000000000000000000000000000000000..3911b881b415f48aa8820ecd743b4343ea18c2b2 --- /dev/null +++ b/linter-4.2/test/property_access_by_index.ts @@ -0,0 +1,30 @@ +/* + * 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. + */ + +// #14071 +class A { + v: string = ''; +} +function SetProperty(oldObj: T, str: string, obj: Object): void { + oldObj[str] = obj; // Should report error +} +function GetProperty(oldObj: T, str: string): U { + return oldObj[str]; // Should report error +} +function test() { + let a: A = { v: 'abc' }; + SetProperty(a, 'u', 'def'); + return GetProperty(a, 'v') + GetProperty(a, 'u'); +} diff --git a/linter-4.2/test/property_access_by_index.ts.autofix.json b/linter-4.2/test/property_access_by_index.ts.autofix.json new file mode 100644 index 0000000000000000000000000000000000000000..90d724616880387c950082972e08f2258bd8d910 --- /dev/null +++ b/linter-4.2/test/property_access_by_index.ts.autofix.json @@ -0,0 +1,34 @@ +{ + "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": 21, + "column": 3, + "problem": "PropertyAccessByIndex", + "autofixable": false, + "suggest": "", + "rule": "Indexed access is not supported for fields (arkts-no-props-by-index)" + }, + { + "line": 24, + "column": 10, + "problem": "PropertyAccessByIndex", + "autofixable": false, + "suggest": "", + "rule": "Indexed access is not supported for fields (arkts-no-props-by-index)" + } + ] +} \ No newline at end of file diff --git a/linter-4.2/test/property_access_by_index.ts.relax.json b/linter-4.2/test/property_access_by_index.ts.relax.json new file mode 100644 index 0000000000000000000000000000000000000000..e7d2d6779bbeb56cae88903ecb7da87087831260 --- /dev/null +++ b/linter-4.2/test/property_access_by_index.ts.relax.json @@ -0,0 +1,17 @@ +{ + "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": [] +} \ No newline at end of file diff --git a/linter-4.2/test/property_access_by_index.ts.strict.json b/linter-4.2/test/property_access_by_index.ts.strict.json new file mode 100644 index 0000000000000000000000000000000000000000..816f247c72632d9fd99dbda580227fe028e3bf32 --- /dev/null +++ b/linter-4.2/test/property_access_by_index.ts.strict.json @@ -0,0 +1,32 @@ +{ + "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": 21, + "column": 3, + "problem": "PropertyAccessByIndex", + "suggest": "", + "rule": "Indexed access is not supported for fields (arkts-no-props-by-index)" + }, + { + "line": 24, + "column": 10, + "problem": "PropertyAccessByIndex", + "suggest": "", + "rule": "Indexed access is not supported for fields (arkts-no-props-by-index)" + } + ] +} \ No newline at end of file diff --git a/linter/scripts/update-test-results.mjs b/linter/scripts/update-test-results.mjs index dc579e42453a4416215504af7d0e3133221a4f33..45029c37eb3f2858d3a3848b5eebc2453598d268 100644 --- a/linter/scripts/update-test-results.mjs +++ b/linter/scripts/update-test-results.mjs @@ -37,7 +37,7 @@ const RESULTS_DIR = 'results' let testDirs = []; // forces to update all tests regardless of whether there was diff in a test result -const force_update = false; +let force_update = false; for (let arg of process.argv.slice(2)) { if (arg === '--force') @@ -72,33 +72,37 @@ function readTestFile(filePath) { function updateTest(testDir, testFile, mode) { let resultExt = RESULT_EXT[mode]; - let testFileWithExt = testFile + resultExt; + let resultFileWithExt = testFile + resultExt; + let resultFilePath = path.join(testDir, resultFileWithExt); // Do not update autofix result if test is skipped - if (mode === Mode.AUTOFIX && fs.existsSync(path.join(testDir, testFileWithExt + AUTOFIX_SKIP_EXT))) { + if (mode === Mode.AUTOFIX && fs.existsSync(path.join(testDir, testFile + AUTOFIX_SKIP_EXT))) { return; } - // Update test result when 'diff' exists or the 'force' option is enabled. - if (!fs.existsSync(path.join(testDir, RESULTS_DIR, testFileWithExt + DIFF_EXT)) && !force_update) { + // Update test result when: + // - '.diff' exists + // - expected '.json' doesn't exist + // - 'force' option is enabled + if (fs.existsSync(resultFilePath) && !fs.existsSync(path.join(testDir, RESULTS_DIR, resultFileWithExt + DIFF_EXT)) && !force_update) { return; } - let expectedResult = readTestFile(path.join(testDir, testFileWithExt)); + let expectedResult = readTestFile(resultFilePath); const copyright = expectedResult?.copyright ?? DEFAULT_COPYRIGHT; - let actualResult = readTestFile(path.join(testDir, RESULTS_DIR, testFileWithExt)); + let actualResult = readTestFile(path.join(testDir, RESULTS_DIR, resultFileWithExt)); if (!actualResult || !actualResult.nodes) { - console.log(`Failed to update ${testFileWithExt}: couldn't read ACTUAL result file.`); + console.log(`Failed to update ${resultFileWithExt}: couldn't read ACTUAL result file.`); return; } // Write file with actual test results. let newResultJSON = JSON.stringify({ copyright, nodes: actualResult.nodes }, null, 4); - fs.writeFileSync(path.join(testDir, testFileWithExt), newResultJSON); + fs.writeFileSync(resultFilePath, newResultJSON); - console.log(`Updated ${testFileWithExt}`); + console.log(`Updated ${resultFileWithExt}`); } for (let testDir of testDirs) { diff --git a/linter/src/TypeScriptLinter.ts b/linter/src/TypeScriptLinter.ts index 2b9e6c52ec3fb4f15780d9b7ae7470cd32c42bba..16a73043dd9f3d07e59952e1b5a974bdf78d9b04 100644 --- a/linter/src/TypeScriptLinter.ts +++ b/linter/src/TypeScriptLinter.ts @@ -1313,7 +1313,7 @@ export class TypeScriptLinter { private handleElementAccessExpression(node: ts.Node) { const tsElementAccessExpr = node as ts.ElementAccessExpression; - const tsElemAccessBaseExprType = this.tsTypeChecker.getTypeAtLocation(tsElementAccessExpr.expression); + const tsElemAccessBaseExprType = this.tsUtils.getTypeOrTypeConstraintAtLocation(tsElementAccessExpr.expression); const tsElemAccessBaseExprTypeNode = this.tsTypeChecker.typeToTypeNode(tsElemAccessBaseExprType, undefined, ts.NodeBuilderFlags.None); const checkClassOrInterface = tsElemAccessBaseExprType.isClassOrInterface() && diff --git a/linter/src/utils/TsUtils.ts b/linter/src/utils/TsUtils.ts index 930520906a0c482a5e5c07b4c5eb124c17e4219d..3e83ebc0de91eafa3f1fdebe8390606de3c27d79 100644 --- a/linter/src/utils/TsUtils.ts +++ b/linter/src/utils/TsUtils.ts @@ -1329,4 +1329,15 @@ export class TsUtils { visitNode(funcExpr); return found; } + + public getTypeOrTypeConstraintAtLocation(expr: ts.Expression): ts.Type { + let type = this.tsTypeChecker.getTypeAtLocation(expr); + if (type.isTypeParameter()) { + let constraint = type.getConstraint(); + if (constraint) { + return constraint; + } + } + return type; + } } diff --git a/linter/test/property_access_by_index.ts b/linter/test/property_access_by_index.ts new file mode 100644 index 0000000000000000000000000000000000000000..3911b881b415f48aa8820ecd743b4343ea18c2b2 --- /dev/null +++ b/linter/test/property_access_by_index.ts @@ -0,0 +1,30 @@ +/* + * 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. + */ + +// #14071 +class A { + v: string = ''; +} +function SetProperty(oldObj: T, str: string, obj: Object): void { + oldObj[str] = obj; // Should report error +} +function GetProperty(oldObj: T, str: string): U { + return oldObj[str]; // Should report error +} +function test() { + let a: A = { v: 'abc' }; + SetProperty(a, 'u', 'def'); + return GetProperty(a, 'v') + GetProperty(a, 'u'); +} diff --git a/linter/test/property_access_by_index.ts.autofix.json b/linter/test/property_access_by_index.ts.autofix.json new file mode 100644 index 0000000000000000000000000000000000000000..90d724616880387c950082972e08f2258bd8d910 --- /dev/null +++ b/linter/test/property_access_by_index.ts.autofix.json @@ -0,0 +1,34 @@ +{ + "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": 21, + "column": 3, + "problem": "PropertyAccessByIndex", + "autofixable": false, + "suggest": "", + "rule": "Indexed access is not supported for fields (arkts-no-props-by-index)" + }, + { + "line": 24, + "column": 10, + "problem": "PropertyAccessByIndex", + "autofixable": false, + "suggest": "", + "rule": "Indexed access is not supported for fields (arkts-no-props-by-index)" + } + ] +} \ No newline at end of file diff --git a/linter/test/property_access_by_index.ts.relax.json b/linter/test/property_access_by_index.ts.relax.json new file mode 100644 index 0000000000000000000000000000000000000000..e7d2d6779bbeb56cae88903ecb7da87087831260 --- /dev/null +++ b/linter/test/property_access_by_index.ts.relax.json @@ -0,0 +1,17 @@ +{ + "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": [] +} \ No newline at end of file diff --git a/linter/test/property_access_by_index.ts.strict.json b/linter/test/property_access_by_index.ts.strict.json new file mode 100644 index 0000000000000000000000000000000000000000..816f247c72632d9fd99dbda580227fe028e3bf32 --- /dev/null +++ b/linter/test/property_access_by_index.ts.strict.json @@ -0,0 +1,32 @@ +{ + "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": 21, + "column": 3, + "problem": "PropertyAccessByIndex", + "suggest": "", + "rule": "Indexed access is not supported for fields (arkts-no-props-by-index)" + }, + { + "line": 24, + "column": 10, + "problem": "PropertyAccessByIndex", + "suggest": "", + "rule": "Indexed access is not supported for fields (arkts-no-props-by-index)" + } + ] +} \ No newline at end of file