diff --git a/linter-4.2/src/TypeScriptLinter.ts b/linter-4.2/src/TypeScriptLinter.ts index 5fe2f80a4120850c7178224a1ab4d59b11a2b273..68d9f03893026151229454bf1db03cf6515bdc42 100644 --- a/linter-4.2/src/TypeScriptLinter.ts +++ b/linter-4.2/src/TypeScriptLinter.ts @@ -1570,7 +1570,7 @@ export class TypeScriptLinter { return; } - if ((tsIdentSym.flags & illegalValues) == 0 || !this.identiferUseInValueContext(tsIdentifier)) { + if ((tsIdentSym.flags & illegalValues) == 0 || !this.identiferUseInValueContext(tsIdentifier, tsIdentSym)) { return; } if (tsIdentSym.flags & ts.SymbolFlags.ValueModule) { @@ -1582,36 +1582,39 @@ export class TypeScriptLinter { } private identiferUseInValueContext( - tsIdentifier: ts.Identifier + ident: ts.Identifier, tsSym: ts.Symbol ) { // If identifier is the right-most name of Property Access chain or Qualified name, // or it's a separate identifier expression, then identifier is being referenced as an value. - let tsIdentStart: ts.Node = tsIdentifier; - while (ts.isPropertyAccessExpression(tsIdentStart.parent) || ts.isQualifiedName(tsIdentStart.parent)) { - tsIdentStart = tsIdentStart.parent; + let qualifiedStart: ts.Node = ident; + while (ts.isPropertyAccessExpression(qualifiedStart.parent) || ts.isQualifiedName(qualifiedStart.parent)) { + qualifiedStart = qualifiedStart.parent; } + let parent = qualifiedStart.parent; return !( // treat TypeQuery as valid because it's already forbidden (FaultID.TypeQuery) - (ts.isTypeNode(tsIdentStart.parent) && !ts.isTypeOfExpression(tsIdentStart.parent)) || - ts.isExpressionWithTypeArguments(tsIdentStart.parent) || - ts.isExportAssignment(tsIdentStart.parent) || - ts.isExportSpecifier(tsIdentStart.parent) || - ts.isMetaProperty(tsIdentStart.parent) || - ts.isImportClause(tsIdentStart.parent) || - ts.isClassLike(tsIdentStart.parent) || - ts.isInterfaceDeclaration(tsIdentStart.parent) || - ts.isModuleDeclaration(tsIdentStart.parent) || - ts.isEnumDeclaration(tsIdentStart.parent) || - ts.isNamespaceImport(tsIdentStart.parent) || - ts.isImportSpecifier(tsIdentStart.parent) || - // rightmost in AST is rightmost in qualified name chain - (ts.isQualifiedName(tsIdentStart) && tsIdentifier !== tsIdentStart.right) || - (ts.isPropertyAccessExpression(tsIdentStart) && - tsIdentifier !== tsIdentStart.name) || - (ts.isNewExpression(tsIdentStart.parent) && - tsIdentStart === tsIdentStart.parent.expression) || - (ts.isBinaryExpression(tsIdentStart.parent) && - tsIdentStart.parent.operatorToken.kind === + (ts.isTypeNode(parent) && !ts.isTypeOfExpression(parent)) || + // ElementAccess is allowed for enum types + (ts.isElementAccessExpression(parent) + && (parent as ts.ElementAccessExpression).expression == ident && (tsSym.flags & ts.SymbolFlags.Enum)) || + ts.isExpressionWithTypeArguments(parent) || + ts.isExportAssignment(parent) || + ts.isExportSpecifier(parent) || + ts.isMetaProperty(parent) || + ts.isImportClause(parent) || + ts.isClassLike(parent) || + ts.isInterfaceDeclaration(parent) || + ts.isModuleDeclaration(parent) || + ts.isEnumDeclaration(parent) || + ts.isNamespaceImport(parent) || + ts.isImportSpecifier(parent) || + (ts.isQualifiedName(qualifiedStart) && ident !== qualifiedStart.right) || + (ts.isPropertyAccessExpression(qualifiedStart) && + ident !== qualifiedStart.name) || + (ts.isNewExpression(qualifiedStart.parent) && + qualifiedStart === qualifiedStart.parent.expression) || + (ts.isBinaryExpression(qualifiedStart.parent) && + qualifiedStart.parent.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword) ); } @@ -1630,14 +1633,22 @@ export class TypeScriptLinter { !this.tsUtils.isGenericArrayType(tsElemAccessBaseExprType) && !this.tsUtils.isDerivedFromArray(tsElemAccessBaseExprType); const checkThisOrSuper = this.tsUtils.isThisOrSuperExpr(tsElementAccessExpr.expression) && - !this.tsUtils.isDerivedFromArray(tsElemAccessBaseExprType); + !this.tsUtils.isDerivedFromArray(tsElemAccessBaseExprType); + + // if (this.tsUtils.isEnumType(tsElemAccessBaseExprType)) { + // implement argument expression type check + // let argType = this.tsTypeChecker.getTypeAtLocation(tsElementAccessExpr.argumentExpression); + // if (argType.aliasSymbol == this.tsUtils.trueSymbolAtLocation(tsElementAccessExpr.expression)) { + // return; + // } + // check if constant EnumMember inferred ... + // this.incrementCounters(node, FaultID.PropertyAccessByIndex, autofixable, autofix); + // } if ( !this.tsUtils.isLibraryType(tsElemAccessBaseExprType) && !this.tsUtils.isTypedArray(tsElemAccessBaseExprTypeNode) && ( checkClassOrInterface || - this.tsUtils.isObjectLiteralType(tsElemAccessBaseExprType) || - this.tsUtils.isEnumType(tsElemAccessBaseExprType) || - checkThisOrSuper) + this.tsUtils.isObjectLiteralType(tsElemAccessBaseExprType) || checkThisOrSuper) ) { let autofix = Autofixer.fixPropertyAccessByIndex(node); const autofixable = autofix != undefined; diff --git a/linter-4.2/test/enum_indexing.ts b/linter-4.2/test/enum_indexing.ts new file mode 100644 index 0000000000000000000000000000000000000000..d669617ce206b42e670dc5aed125032401d86a2f --- /dev/null +++ b/linter-4.2/test/enum_indexing.ts @@ -0,0 +1,28 @@ +/* + * 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. + */ + +enum E { a = 1, b = 2 }; + +let ill = E; + +// legal +E[E.a]; +(e: E.a) => E[e]; +(e: E) => E[e]; + +// check required in future +E[1]; +(e: E.a | E.b) => E[e]; +(e: number) => E[e]; diff --git a/linter-4.2/test/enum_indexing.ts.autofix.skip b/linter-4.2/test/enum_indexing.ts.autofix.skip new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/linter-4.2/test/enum_indexing.ts.relax.json b/linter-4.2/test/enum_indexing.ts.relax.json new file mode 100644 index 0000000000000000000000000000000000000000..1cb184ed348796563334f02bb62bfc50c059f380 --- /dev/null +++ b/linter-4.2/test/enum_indexing.ts.relax.json @@ -0,0 +1,23 @@ +{ + "copyright": [ + "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." + ], + "nodes": [ + { + "line": 18, + "column": 11, + "problem": "ClassAsObject" + } + ] +} \ No newline at end of file diff --git a/linter-4.2/test/enum_indexing.ts.strict.json b/linter-4.2/test/enum_indexing.ts.strict.json new file mode 100644 index 0000000000000000000000000000000000000000..1cb184ed348796563334f02bb62bfc50c059f380 --- /dev/null +++ b/linter-4.2/test/enum_indexing.ts.strict.json @@ -0,0 +1,23 @@ +{ + "copyright": [ + "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." + ], + "nodes": [ + { + "line": 18, + "column": 11, + "problem": "ClassAsObject" + } + ] +} \ No newline at end of file diff --git a/linter-4.2/test/imported_use_as_object.ts b/linter-4.2/test/imported_use_as_object.ts index da742fba253aae032db5a7ecab3fc3afb6fcad12..caed917a7ead85f9d097c61e4b2b12f19c06133c 100644 --- a/linter-4.2/test/imported_use_as_object.ts +++ b/linter-4.2/test/imported_use_as_object.ts @@ -45,3 +45,6 @@ typeof InnerN; foo1(() => InnerE); foo2(InnerE); typeof InnerE; + +OuterE[OuterE.e]; +InnerE[InnerE.e]; diff --git a/linter/src/TypeScriptLinter.ts b/linter/src/TypeScriptLinter.ts index 84cba26f7de2979d99b7372404d8780d28ab24c4..7f9802d498e4ffd5035b80a3458ad318337f7a26 100644 --- a/linter/src/TypeScriptLinter.ts +++ b/linter/src/TypeScriptLinter.ts @@ -1290,7 +1290,7 @@ export class TypeScriptLinter { return; } - if ((tsIdentSym.flags & illegalValues) == 0 || !identiferUseInValueContext(tsIdentifier)) { + if ((tsIdentSym.flags & illegalValues) == 0 || !identiferUseInValueContext(tsIdentifier, tsIdentSym)) { return; } if (tsIdentSym.flags & ts.SymbolFlags.ValueModule) { @@ -1310,11 +1310,13 @@ export class TypeScriptLinter { !this.tsUtils.isGenericArrayType(tsElemAccessBaseExprType) && !this.tsUtils.isDerivedFromArray(tsElemAccessBaseExprType); const checkThisOrSuper = this.tsUtils.isThisOrSuperExpr(tsElementAccessExpr.expression) && - !this.tsUtils.isDerivedFromArray(tsElemAccessBaseExprType); + !this.tsUtils.isDerivedFromArray(tsElemAccessBaseExprType); + + // implement check for enum argument expression + if ( !this.tsUtils.isLibraryType(tsElemAccessBaseExprType) && !this.tsUtils.isTypedArray(tsElemAccessBaseExprTypeNode) && - (checkClassOrInterface || this.tsUtils.isObjectLiteralType(tsElemAccessBaseExprType) || - this.tsUtils.isEnumType(tsElemAccessBaseExprType) || checkThisOrSuper) + (checkClassOrInterface || this.tsUtils.isObjectLiteralType(tsElemAccessBaseExprType) || checkThisOrSuper) ) { let autofix = Autofixer.fixPropertyAccessByIndex(node); const autofixable = autofix != undefined; diff --git a/linter/src/utils/functions/identiferUseInValueContext.ts b/linter/src/utils/functions/identiferUseInValueContext.ts index 908dde4da9669bf48a13e7ab9001f0b8c60eb8cd..90fdc4281faf559603c6f9eeffee6736b421c95b 100644 --- a/linter/src/utils/functions/identiferUseInValueContext.ts +++ b/linter/src/utils/functions/identiferUseInValueContext.ts @@ -35,31 +35,35 @@ function isPropertyAccessContext(tsIdentStart: ts.Node, tsIdentifier: ts.Identif } export function identiferUseInValueContext( - tsIdentifier: ts.Identifier + ident: ts.Identifier, tsSym: ts.Symbol ) { // If identifier is the right-most name of Property Access chain or Qualified name, // or it's a separate identifier expression, then identifier is being referenced as an value. - let tsIdentStart: ts.Node = tsIdentifier; - while (ts.isPropertyAccessExpression(tsIdentStart.parent) || ts.isQualifiedName(tsIdentStart.parent)) { - tsIdentStart = tsIdentStart.parent; + let qualifiedStart: ts.Node = ident; + while (ts.isPropertyAccessExpression(qualifiedStart.parent) || ts.isQualifiedName(qualifiedStart.parent)) { + qualifiedStart = qualifiedStart.parent; } + let parent = qualifiedStart.parent; return !( // treat TypeQuery as valid because it's already forbidden (FaultID.TypeQuery) - (ts.isTypeNode(tsIdentStart.parent) && !ts.isTypeOfExpression(tsIdentStart.parent)) || - ts.isExpressionWithTypeArguments(tsIdentStart.parent) || - ts.isExportAssignment(tsIdentStart.parent) || - ts.isExportSpecifier(tsIdentStart.parent) || - ts.isMetaProperty(tsIdentStart.parent) || - ts.isImportClause(tsIdentStart.parent) || - ts.isClassLike(tsIdentStart.parent) || - ts.isInterfaceDeclaration(tsIdentStart.parent) || - ts.isModuleDeclaration(tsIdentStart.parent) || - ts.isEnumDeclaration(tsIdentStart.parent) || - ts.isNamespaceImport(tsIdentStart.parent) || - ts.isImportSpecifier(tsIdentStart.parent) || - isQualifiedNameContext(tsIdentStart, tsIdentifier) || - isPropertyAccessContext(tsIdentStart, tsIdentifier) || - isNewExpressionContext(tsIdentStart) || - isInstanceofContext(tsIdentStart) + (ts.isTypeNode(parent) && !ts.isTypeOfExpression(parent)) || + // ElementAccess is allowed for enum types + (ts.isElementAccessExpression(parent) + && (parent as ts.ElementAccessExpression).expression == ident && (tsSym.flags & ts.SymbolFlags.Enum)) || + ts.isExpressionWithTypeArguments(parent) || + ts.isExportAssignment(parent) || + ts.isExportSpecifier(parent) || + ts.isMetaProperty(parent) || + ts.isImportClause(parent) || + ts.isClassLike(parent) || + ts.isInterfaceDeclaration(parent) || + ts.isModuleDeclaration(parent) || + ts.isEnumDeclaration(parent) || + ts.isNamespaceImport(parent) || + ts.isImportSpecifier(parent) || + isQualifiedNameContext(qualifiedStart, ident) || + isPropertyAccessContext(qualifiedStart, ident) || + isNewExpressionContext(qualifiedStart) || + isInstanceofContext(qualifiedStart) ); } diff --git a/linter/test/enum_indexing.ts b/linter/test/enum_indexing.ts new file mode 100644 index 0000000000000000000000000000000000000000..d669617ce206b42e670dc5aed125032401d86a2f --- /dev/null +++ b/linter/test/enum_indexing.ts @@ -0,0 +1,28 @@ +/* + * 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. + */ + +enum E { a = 1, b = 2 }; + +let ill = E; + +// legal +E[E.a]; +(e: E.a) => E[e]; +(e: E) => E[e]; + +// check required in future +E[1]; +(e: E.a | E.b) => E[e]; +(e: number) => E[e]; diff --git a/linter/test/enum_indexing.ts.autofix.skip b/linter/test/enum_indexing.ts.autofix.skip new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/linter/test/enum_indexing.ts.relax.json b/linter/test/enum_indexing.ts.relax.json new file mode 100644 index 0000000000000000000000000000000000000000..1cb184ed348796563334f02bb62bfc50c059f380 --- /dev/null +++ b/linter/test/enum_indexing.ts.relax.json @@ -0,0 +1,23 @@ +{ + "copyright": [ + "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." + ], + "nodes": [ + { + "line": 18, + "column": 11, + "problem": "ClassAsObject" + } + ] +} \ No newline at end of file diff --git a/linter/test/enum_indexing.ts.strict.json b/linter/test/enum_indexing.ts.strict.json new file mode 100644 index 0000000000000000000000000000000000000000..1cb184ed348796563334f02bb62bfc50c059f380 --- /dev/null +++ b/linter/test/enum_indexing.ts.strict.json @@ -0,0 +1,23 @@ +{ + "copyright": [ + "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." + ], + "nodes": [ + { + "line": 18, + "column": 11, + "problem": "ClassAsObject" + } + ] +} \ No newline at end of file diff --git a/linter/test/imported_use_as_object.ts b/linter/test/imported_use_as_object.ts index da742fba253aae032db5a7ecab3fc3afb6fcad12..caed917a7ead85f9d097c61e4b2b12f19c06133c 100644 --- a/linter/test/imported_use_as_object.ts +++ b/linter/test/imported_use_as_object.ts @@ -45,3 +45,6 @@ typeof InnerN; foo1(() => InnerE); foo2(InnerE); typeof InnerE; + +OuterE[OuterE.e]; +InnerE[InnerE.e];