From 1b941efeabc30f42202515853ea5fe0c409178ca Mon Sep 17 00:00:00 2001 From: Ilya Trubachev Date: Mon, 4 Sep 2023 19:00:20 +0300 Subject: [PATCH] fix index access to inherited class of array Signed-off-by: Ilya Trubachev --- linter-4.2/src/TypeScriptLinter.ts | 10 ++- linter-4.2/src/Utils.ts | 22 +++++++ linter-4.2/test/indexed_derived_from_array.ts | 61 +++++++++++++++++++ ...indexed_derived_from_array.ts.autofix.skip | 0 .../indexed_derived_from_array.ts.relax.json | 17 ++++++ .../indexed_derived_from_array.ts.strict.json | 17 ++++++ linter/src/TypeScriptLinter.ts | 11 +++- linter/src/utils/TsUtils.ts | 22 +++++++ linter/test/indexed_derived_from_array.ts | 61 +++++++++++++++++++ ...indexed_derived_from_array.ts.autofix.skip | 0 .../indexed_derived_from_array.ts.relax.json | 17 ++++++ .../indexed_derived_from_array.ts.strict.json | 17 ++++++ 12 files changed, 249 insertions(+), 6 deletions(-) create mode 100644 linter-4.2/test/indexed_derived_from_array.ts create mode 100644 linter-4.2/test/indexed_derived_from_array.ts.autofix.skip create mode 100644 linter-4.2/test/indexed_derived_from_array.ts.relax.json create mode 100644 linter-4.2/test/indexed_derived_from_array.ts.strict.json create mode 100644 linter/test/indexed_derived_from_array.ts create mode 100644 linter/test/indexed_derived_from_array.ts.autofix.skip create mode 100644 linter/test/indexed_derived_from_array.ts.relax.json create mode 100644 linter/test/indexed_derived_from_array.ts.strict.json diff --git a/linter-4.2/src/TypeScriptLinter.ts b/linter-4.2/src/TypeScriptLinter.ts index d7622bcc4..187689a2a 100644 --- a/linter-4.2/src/TypeScriptLinter.ts +++ b/linter-4.2/src/TypeScriptLinter.ts @@ -1730,14 +1730,18 @@ export class TypeScriptLinter { undefined, ts.NodeBuilderFlags.None ); + const checkClassOrInterface = tsElemAccessBaseExprType.isClassOrInterface() && + !this.tsUtils.isGenericArrayType(tsElemAccessBaseExprType) && + !this.tsUtils.isDerivedFromArray(tsElemAccessBaseExprType); + const checkThisOrSuper = this.tsUtils.isThisOrSuperExpr(tsElementAccessExpr.expression) && + !this.tsUtils.isDerivedFromArray(tsElemAccessBaseExprType); if ( !this.tsUtils.isLibraryType(tsElemAccessBaseExprType) && !this.tsUtils.isTypedArray(tsElemAccessBaseExprTypeNode) && - ((tsElemAccessBaseExprType.isClassOrInterface() && - !this.tsUtils.isGenericArrayType(tsElemAccessBaseExprType)) || + ( checkClassOrInterface || this.tsUtils.isObjectLiteralType(tsElemAccessBaseExprType) || this.tsUtils.isEnumType(tsElemAccessBaseExprType) || - this.tsUtils.isThisOrSuperExpr(tsElementAccessExpr.expression)) + checkThisOrSuper) ) { let autofix = Autofixer.fixPropertyAccessByIndex(node); const autofixable = autofix != undefined; diff --git a/linter-4.2/src/Utils.ts b/linter-4.2/src/Utils.ts index 2ca1cdc8a..bca40b2d1 100644 --- a/linter-4.2/src/Utils.ts +++ b/linter-4.2/src/Utils.ts @@ -372,6 +372,28 @@ export class TsUtils { ); } + // does something similar to relatedByInheritanceOrIdentical function + public isDerivedFromArray(tsType: ts.Type): tsType is ts.TypeReference { + if (this.isTypeReference(tsType) && tsType.target !== tsType) tsType = tsType.target; + if (!tsType.symbol || !tsType.symbol.declarations) return false; + + for (let tsTypeDecl of tsType.symbol.declarations) { + if ( + (!ts.isClassDeclaration(tsTypeDecl) && !ts.isInterfaceDeclaration(tsTypeDecl)) || + !tsTypeDecl.heritageClauses + ) break; + for (let heritageClause of tsTypeDecl.heritageClauses) { + for (let baseTypeExpr of heritageClause.types) { + let baseType = this.tsTypeChecker.getTypeAtLocation(baseTypeExpr); + if (this.isTypeReference(baseType) && baseType.target !== baseType) baseType = baseType.target; + const baseTypeNode = this.tsTypeChecker.typeToTypeNode(baseType, undefined, ts.NodeBuilderFlags.None); + return this.isGenericArrayType(baseType) || this.isTypedArray(baseTypeNode); + } + } + } + return false; + } + public isTypeReference(tsType: ts.Type): tsType is ts.TypeReference { return ( (tsType.getFlags() & ts.TypeFlags.Object) !== 0 && diff --git a/linter-4.2/test/indexed_derived_from_array.ts b/linter-4.2/test/indexed_derived_from_array.ts new file mode 100644 index 000000000..2af5337bc --- /dev/null +++ b/linter-4.2/test/indexed_derived_from_array.ts @@ -0,0 +1,61 @@ +/* + * 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. + */ + +let nextId: number = 0 +class ObservedArray extends Array {} + +let arr1 = new ObservedArray(1, 2, 3) +console.log(arr1[0]) +console.log(arr1[nextId]) + +class NumberArray extends Array {} +let arr2 = new NumberArray(4, 5, 6) +console.log(arr2[0]) +console.log(arr2[nextId]) + +class NumberTypedArray extends Uint32Array {} +let arr3 = new NumberTypedArray([7, 8, 9]) +console.log(arr3[0]) +console.log(arr3[nextId]) + +class Point extends Array { + constructor(x: T, y: T) { + super(x, y) + } + + public add(rhs: Point): Point { + this[0] = this.compare(this[0], rhs[0], true); + return this; + } + + private compare(left: T, right: T, flag:boolean): T { + return flag ? left : right + } +} + +class PointNumber extends Array { + constructor(x: number, y: number) { + super(x, y) + } + + public add(rhs: PointNumber): PointNumber { + this[0] = this.compare(this[0], rhs[0], true); + return this; + } + + private compare(left: number, right: number, flag: boolean): number { + return flag ? left : right + } +} diff --git a/linter-4.2/test/indexed_derived_from_array.ts.autofix.skip b/linter-4.2/test/indexed_derived_from_array.ts.autofix.skip new file mode 100644 index 000000000..e69de29bb diff --git a/linter-4.2/test/indexed_derived_from_array.ts.relax.json b/linter-4.2/test/indexed_derived_from_array.ts.relax.json new file mode 100644 index 000000000..f3fdebc36 --- /dev/null +++ b/linter-4.2/test/indexed_derived_from_array.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": [] +} diff --git a/linter-4.2/test/indexed_derived_from_array.ts.strict.json b/linter-4.2/test/indexed_derived_from_array.ts.strict.json new file mode 100644 index 000000000..f3fdebc36 --- /dev/null +++ b/linter-4.2/test/indexed_derived_from_array.ts.strict.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": [] +} diff --git a/linter/src/TypeScriptLinter.ts b/linter/src/TypeScriptLinter.ts index ee117b90f..9d0077c19 100644 --- a/linter/src/TypeScriptLinter.ts +++ b/linter/src/TypeScriptLinter.ts @@ -1326,11 +1326,16 @@ export class TypeScriptLinter { const tsElementAccessExpr = node as ts.ElementAccessExpression; const tsElemAccessBaseExprType = this.tsTypeChecker.getTypeAtLocation(tsElementAccessExpr.expression); const tsElemAccessBaseExprTypeNode = this.tsTypeChecker.typeToTypeNode(tsElemAccessBaseExprType, undefined, ts.NodeBuilderFlags.None); + + const checkClassOrInterface = tsElemAccessBaseExprType.isClassOrInterface() && + !this.tsUtils.isGenericArrayType(tsElemAccessBaseExprType) && + !this.tsUtils.isDerivedFromArray(tsElemAccessBaseExprType); + const checkThisOrSuper = this.tsUtils.isThisOrSuperExpr(tsElementAccessExpr.expression) && + !this.tsUtils.isDerivedFromArray(tsElemAccessBaseExprType); if ( !this.tsUtils.isLibraryType(tsElemAccessBaseExprType) && !this.tsUtils.isTypedArray(tsElemAccessBaseExprTypeNode) && - ((tsElemAccessBaseExprType.isClassOrInterface() && !this.tsUtils.isGenericArrayType(tsElemAccessBaseExprType)) || - this.tsUtils.isObjectLiteralType(tsElemAccessBaseExprType) || this.tsUtils.isEnumType(tsElemAccessBaseExprType) || - this.tsUtils.isThisOrSuperExpr(tsElementAccessExpr.expression)) + (checkClassOrInterface || this.tsUtils.isObjectLiteralType(tsElemAccessBaseExprType) || + this.tsUtils.isEnumType(tsElemAccessBaseExprType) || checkThisOrSuper) ) { let autofix = Autofixer.fixPropertyAccessByIndex(node); const autofixable = autofix != undefined; diff --git a/linter/src/utils/TsUtils.ts b/linter/src/utils/TsUtils.ts index 0ded4c929..dd2644b67 100644 --- a/linter/src/utils/TsUtils.ts +++ b/linter/src/utils/TsUtils.ts @@ -232,6 +232,28 @@ export class TsUtils { ); } + // does something similar to relatedByInheritanceOrIdentical function + public isDerivedFromArray(tsType: ts.Type): tsType is ts.TypeReference { + if (this.isTypeReference(tsType) && tsType.target !== tsType) tsType = tsType.target; + if (!tsType.symbol || !tsType.symbol.declarations) return false; + + for (let tsTypeDecl of tsType.symbol.declarations) { + if ( + (!ts.isClassDeclaration(tsTypeDecl) && !ts.isInterfaceDeclaration(tsTypeDecl)) || + !tsTypeDecl.heritageClauses + ) break; + for (let heritageClause of tsTypeDecl.heritageClauses) { + for (let baseTypeExpr of heritageClause.types) { + let baseType = this.tsTypeChecker.getTypeAtLocation(baseTypeExpr); + if (this.isTypeReference(baseType) && baseType.target !== baseType) baseType = baseType.target; + const baseTypeNode = this.tsTypeChecker.typeToTypeNode(baseType, undefined, ts.NodeBuilderFlags.None); + return this.isGenericArrayType(baseType) || this.isTypedArray(baseTypeNode); + } + } + } + return false; + } + public isTypeReference(tsType: ts.Type): tsType is ts.TypeReference { return ( (tsType.getFlags() & ts.TypeFlags.Object) !== 0 && diff --git a/linter/test/indexed_derived_from_array.ts b/linter/test/indexed_derived_from_array.ts new file mode 100644 index 000000000..2af5337bc --- /dev/null +++ b/linter/test/indexed_derived_from_array.ts @@ -0,0 +1,61 @@ +/* + * 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. + */ + +let nextId: number = 0 +class ObservedArray extends Array {} + +let arr1 = new ObservedArray(1, 2, 3) +console.log(arr1[0]) +console.log(arr1[nextId]) + +class NumberArray extends Array {} +let arr2 = new NumberArray(4, 5, 6) +console.log(arr2[0]) +console.log(arr2[nextId]) + +class NumberTypedArray extends Uint32Array {} +let arr3 = new NumberTypedArray([7, 8, 9]) +console.log(arr3[0]) +console.log(arr3[nextId]) + +class Point extends Array { + constructor(x: T, y: T) { + super(x, y) + } + + public add(rhs: Point): Point { + this[0] = this.compare(this[0], rhs[0], true); + return this; + } + + private compare(left: T, right: T, flag:boolean): T { + return flag ? left : right + } +} + +class PointNumber extends Array { + constructor(x: number, y: number) { + super(x, y) + } + + public add(rhs: PointNumber): PointNumber { + this[0] = this.compare(this[0], rhs[0], true); + return this; + } + + private compare(left: number, right: number, flag: boolean): number { + return flag ? left : right + } +} diff --git a/linter/test/indexed_derived_from_array.ts.autofix.skip b/linter/test/indexed_derived_from_array.ts.autofix.skip new file mode 100644 index 000000000..e69de29bb diff --git a/linter/test/indexed_derived_from_array.ts.relax.json b/linter/test/indexed_derived_from_array.ts.relax.json new file mode 100644 index 000000000..f3fdebc36 --- /dev/null +++ b/linter/test/indexed_derived_from_array.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": [] +} diff --git a/linter/test/indexed_derived_from_array.ts.strict.json b/linter/test/indexed_derived_from_array.ts.strict.json new file mode 100644 index 000000000..f3fdebc36 --- /dev/null +++ b/linter/test/indexed_derived_from_array.ts.strict.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": [] +} -- Gitee