1/*
2 * Copyright (c) 2022-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import * as ts from 'typescript';
17
18function isInstanceofContext(tsIdentStart: ts.Node): boolean {
19  return (
20    ts.isBinaryExpression(tsIdentStart.parent) &&
21    tsIdentStart.parent.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword
22  );
23}
24
25function isNewExpressionContext(tsIdentStart: ts.Node): boolean {
26  return ts.isNewExpression(tsIdentStart.parent) && tsIdentStart === tsIdentStart.parent.expression;
27}
28
29/*
30 * If identifier is the right-most name of Property Access chain or Qualified name,
31 * or it's a separate identifier expression, then identifier is being referenced as an value.
32 */
33function isQualifiedNameContext(tsIdentStart: ts.Node, tsIdentifier: ts.Identifier): boolean {
34  // rightmost in AST is rightmost in qualified name chain
35  return ts.isQualifiedName(tsIdentStart) && tsIdentifier !== tsIdentStart.right;
36}
37
38function isPropertyAccessContext(tsIdentStart: ts.Node, tsIdentifier: ts.Identifier): boolean {
39  // rightmost in AST is rightmost in qualified name chain
40  return ts.isPropertyAccessExpression(tsIdentStart) && tsIdentifier !== tsIdentStart.name;
41}
42
43function getQualifiedStart(ident: ts.Node): ts.Node {
44  let qualifiedStart: ts.Node = ident;
45  while (ts.isPropertyAccessExpression(qualifiedStart.parent) || ts.isQualifiedName(qualifiedStart.parent)) {
46    qualifiedStart = qualifiedStart.parent;
47  }
48  return qualifiedStart;
49}
50
51function isEnumPropAccess(ident: ts.Identifier, tsSym: ts.Symbol, context: ts.Node): boolean {
52  return (
53    ts.isElementAccessExpression(context) &&
54    !!(tsSym.flags & ts.SymbolFlags.Enum) &&
55    (context.expression === ident ||
56      ts.isPropertyAccessExpression(context.expression) && context.expression.name === ident)
57  );
58}
59
60function isValidParent(parent: ts.Node): boolean {
61  // treat TypeQuery as valid because it's already forbidden (FaultID.TypeQuery)
62  return (
63    ts.isTypeNode(parent) && !ts.isTypeOfExpression(parent) ||
64    ts.isExpressionWithTypeArguments(parent) ||
65    ts.isExportAssignment(parent) ||
66    ts.isExportSpecifier(parent) ||
67    ts.isMetaProperty(parent) ||
68    ts.isImportClause(parent) ||
69    ts.isClassLike(parent) ||
70    ts.isInterfaceDeclaration(parent) ||
71    ts.isModuleDeclaration(parent) ||
72    ts.isEnumDeclaration(parent) ||
73    ts.isNamespaceImport(parent) ||
74    ts.isImportSpecifier(parent) ||
75    ts.isImportEqualsDeclaration(parent)
76  );
77}
78
79export function identiferUseInValueContext(ident: ts.Identifier, tsSym: ts.Symbol): boolean {
80  const qualifiedStart = getQualifiedStart(ident);
81  const parent = qualifiedStart.parent;
82  const isValidUse =
83    isValidParent(parent) ||
84    isEnumPropAccess(ident, tsSym, parent) ||
85    isQualifiedNameContext(qualifiedStart, ident) ||
86    isPropertyAccessContext(qualifiedStart, ident) ||
87    isNewExpressionContext(qualifiedStart) ||
88    isInstanceofContext(qualifiedStart);
89  return !isValidUse;
90}
91