Skip to content

Commit 3b9b903

Browse files
committed
refactor(SourceFileLinter): Extract common logic to utils
1 parent 3f3c720 commit 3b9b903

File tree

2 files changed

+72
-66
lines changed

2 files changed

+72
-66
lines changed

src/linter/ui5Types/SourceFileLinter.ts

Lines changed: 13 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import {
1515
isClassMethod,
1616
isSourceFileOfPseudoModuleType,
1717
isSourceFileOfTypeScriptLib,
18+
getSymbolModuleDeclaration,
19+
isGlobalThis,
20+
extractNamespace,
21+
extractSapUiNamespace,
1822
} from "./utils/utils.js";
1923
import {taskStart} from "../../utils/perf.js";
2024
import {getPositionsForNode} from "../../utils/nodePosition.js";
@@ -665,7 +669,7 @@ export default class SourceFileLinter {
665669
}
666670
const classType = this.checker.getTypeAtLocation(node.expression);
667671

668-
const moduleDeclaration = this.getSymbolModuleDeclaration(nodeType.symbol);
672+
const moduleDeclaration = getSymbolModuleDeclaration(nodeType.symbol);
669673
if (moduleDeclaration?.name.text === "sap/ui/core/routing/Router") {
670674
this.#analyzeNewCoreRouter(node);
671675
} else if (moduleDeclaration?.name.text === "sap/ui/model/odata/v4/ODataModel") {
@@ -727,47 +731,6 @@ export default class SourceFileLinter {
727731
});
728732
}
729733

730-
extractNamespace(node: ts.PropertyAccessExpression | ts.ElementAccessExpression | ts.CallExpression): string {
731-
const propAccessChain: string[] = [];
732-
propAccessChain.push(node.expression.getText());
733-
734-
let scanNode: ts.Node = node;
735-
while (ts.isPropertyAccessExpression(scanNode)) {
736-
if (!ts.isIdentifier(scanNode.name)) {
737-
throw new Error(
738-
`Unexpected PropertyAccessExpression node: Expected name to be identifier but got ` +
739-
ts.SyntaxKind[scanNode.name.kind]);
740-
}
741-
propAccessChain.push(scanNode.name.text);
742-
scanNode = scanNode.parent;
743-
}
744-
return propAccessChain.join(".");
745-
}
746-
747-
/**
748-
* Extracts the sap.ui API namespace from a symbol name and a module declaration
749-
* (from @sapui5/types sap.ui.core.d.ts), e.g. sap.ui.view.
750-
*/
751-
extractSapUiNamespace(symbolName: string, moduleDeclaration: ts.ModuleDeclaration): string | undefined {
752-
const namespace: string[] = [];
753-
let currentModuleDeclaration: ts.Node | undefined = moduleDeclaration;
754-
while (
755-
currentModuleDeclaration &&
756-
ts.isModuleDeclaration(currentModuleDeclaration) &&
757-
currentModuleDeclaration.flags & ts.NodeFlags.Namespace
758-
) {
759-
namespace.unshift(currentModuleDeclaration.name.text);
760-
currentModuleDeclaration = currentModuleDeclaration.parent?.parent;
761-
}
762-
763-
if (!namespace.length) {
764-
return undefined;
765-
} else {
766-
namespace.push(symbolName);
767-
return namespace.join(".");
768-
}
769-
}
770-
771734
getDeprecationText(deprecatedTag: ts.JSDocTagInfo): string {
772735
// (Workaround) There's an issue in some UI5 TS definition versions and where the
773736
// deprecation text gets merged with the description. Splitting on double
@@ -822,14 +785,14 @@ export default class SourceFileLinter {
822785
throw new Error(`Unhandled CallExpression expression syntax: ${ts.SyntaxKind[exprNode.kind]}`);
823786
}
824787

825-
const moduleDeclaration = this.getSymbolModuleDeclaration(exprType.symbol);
788+
const moduleDeclaration = getSymbolModuleDeclaration(exprType.symbol);
826789
let globalApiName;
827790

828791
if (exprType.symbol && moduleDeclaration) {
829792
const symbolName = exprType.symbol.getName();
830793
const moduleName = moduleDeclaration.name.text;
831794
const nodeType = this.checker.getTypeAtLocation(node);
832-
globalApiName = this.extractSapUiNamespace(symbolName, moduleDeclaration);
795+
globalApiName = extractSapUiNamespace(symbolName, moduleDeclaration);
833796

834797
if (symbolName === "init" && moduleName === "sap/ui/core/Lib") {
835798
// Check for sap/ui/core/Lib.init usages
@@ -919,7 +882,7 @@ export default class SourceFileLinter {
919882
additionalMessage = `of module '${this.checker.typeToString(lhsExprType)}'`;
920883
} else if (ts.isPropertyAccessExpression(exprNode)) {
921884
// left-hand-side is a module or namespace, e.g. "module.deprecatedMethod()"
922-
additionalMessage = `(${this.extractNamespace(exprNode)})`;
885+
additionalMessage = `(${extractNamespace(exprNode)})`;
923886
}
924887
} else if (globalApiName) {
925888
additionalMessage = `(${globalApiName})`;
@@ -952,14 +915,6 @@ export default class SourceFileLinter {
952915
}
953916
}
954917

955-
getSymbolModuleDeclaration(symbol: ts.Symbol) {
956-
let parent = symbol.valueDeclaration?.parent;
957-
while (parent && !ts.isModuleDeclaration(parent)) {
958-
parent = parent.parent;
959-
}
960-
return parent;
961-
}
962-
963918
#analyzeLibInitCall(
964919
node: ts.CallExpression,
965920
exprNode: ts.CallExpression | ts.ElementAccessExpression | ts.PropertyAccessExpression | ts.Identifier) {
@@ -1384,7 +1339,7 @@ export default class SourceFileLinter {
13841339
*/
13851340
#isPropertyBinding(node: ts.NewExpression | ts.CallExpression, propNames: string[]) {
13861341
const controlAmbientModule =
1387-
this.getSymbolModuleDeclaration(this.checker.getTypeAtLocation(node).symbol);
1342+
getSymbolModuleDeclaration(this.checker.getTypeAtLocation(node).symbol);
13881343

13891344
let classArg;
13901345
if (controlAmbientModule?.body && ts.isModuleBlock(controlAmbientModule.body)) {
@@ -1446,7 +1401,7 @@ export default class SourceFileLinter {
14461401
}
14471402
let namespace;
14481403
if (ts.isPropertyAccessExpression(node)) {
1449-
namespace = this.extractNamespace(node);
1404+
namespace = extractNamespace(node);
14501405
}
14511406
if (this.isSymbolOfJquerySapType(deprecationInfo.symbol)) {
14521407
const fixHints = this.getJquerySapFixHints(node);
@@ -1482,14 +1437,6 @@ export default class SourceFileLinter {
14821437
}
14831438
}
14841439

1485-
isGlobalThis(nodeType: string) {
1486-
return [
1487-
"Window & typeof globalThis",
1488-
"typeof globalThis",
1489-
// "Window", // top and parent will resolve to this string, however they are still treated as type 'any'
1490-
].includes(nodeType);
1491-
}
1492-
14931440
analyzeExportedValuesByLib(node: ts.PropertyAccessExpression | ts.ElementAccessExpression) {
14941441
if (!ts.isElementAccessExpression(node) &&
14951442
node.name?.kind !== ts.SyntaxKind.Identifier) {
@@ -1592,7 +1539,7 @@ export default class SourceFileLinter {
15921539

15931540
// Get the NodeType in order to check whether this is indirect global access via Window
15941541
const nodeType = this.checker.getTypeAtLocation(exprNode);
1595-
if (this.isGlobalThis(this.checker.typeToString(nodeType))) {
1542+
if (isGlobalThis(this.checker.typeToString(nodeType))) {
15961543
// In case of Indirect global access we need to check for
15971544
// a global UI5 variable on the right side of the expression instead of left
15981545
if (ts.isPropertyAccessExpression(node)) {
@@ -1613,7 +1560,7 @@ export default class SourceFileLinter {
16131560
if (symbol && this.isSymbolOfUi5OrThirdPartyType(symbol) &&
16141561
!((ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) &&
16151562
this.isAllowedPropertyAccess(node))) {
1616-
const namespace = this.extractNamespace((node as ts.PropertyAccessExpression));
1563+
const namespace = extractNamespace((node as ts.PropertyAccessExpression));
16171564
this.#reporter.addMessage(MESSAGE.NO_GLOBALS, {
16181565
variableName: symbol.getName(),
16191566
namespace,
@@ -1632,7 +1579,7 @@ export default class SourceFileLinter {
16321579
return true;
16331580
}
16341581

1635-
const propAccess = this.extractNamespace(node);
1582+
const propAccess = extractNamespace(node);
16361583
return [
16371584
"sap.ui.define",
16381585
"sap.ui.require",

src/linter/ui5Types/utils/utils.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,62 @@ export function isConditionalAccess(node: ts.Node): boolean {
287287

288288
return false;
289289
}
290+
291+
export function extractNamespace(
292+
node: ts.PropertyAccessExpression | ts.ElementAccessExpression | ts.CallExpression
293+
): string {
294+
const propAccessChain: string[] = [];
295+
propAccessChain.push(node.expression.getText());
296+
297+
let scanNode: ts.Node = node;
298+
while (ts.isPropertyAccessExpression(scanNode)) {
299+
if (!ts.isIdentifier(scanNode.name)) {
300+
throw new Error(
301+
`Unexpected PropertyAccessExpression node: Expected name to be identifier but got ` +
302+
ts.SyntaxKind[scanNode.name.kind]);
303+
}
304+
propAccessChain.push(scanNode.name.text);
305+
scanNode = scanNode.parent;
306+
}
307+
return propAccessChain.join(".");
308+
}
309+
310+
export function isGlobalThis(nodeType: string) {
311+
return [
312+
"Window & typeof globalThis",
313+
"typeof globalThis",
314+
// "Window", // top and parent will resolve to this string, however they are still treated as type 'any'
315+
].includes(nodeType);
316+
}
317+
318+
export function getSymbolModuleDeclaration(symbol: ts.Symbol) {
319+
let parent = symbol.valueDeclaration?.parent;
320+
while (parent && !ts.isModuleDeclaration(parent)) {
321+
parent = parent.parent;
322+
}
323+
return parent;
324+
}
325+
326+
/**
327+
* Extracts the sap.ui API namespace from a symbol name and a module declaration
328+
* (from @sapui5/types sap.ui.core.d.ts), e.g. sap.ui.view.
329+
*/
330+
export function extractSapUiNamespace(symbolName: string, moduleDeclaration: ts.ModuleDeclaration): string | undefined {
331+
const namespace: string[] = [];
332+
let currentModuleDeclaration: ts.Node | undefined = moduleDeclaration;
333+
while (
334+
currentModuleDeclaration &&
335+
ts.isModuleDeclaration(currentModuleDeclaration) &&
336+
currentModuleDeclaration.flags & ts.NodeFlags.Namespace
337+
) {
338+
namespace.unshift(currentModuleDeclaration.name.text);
339+
currentModuleDeclaration = currentModuleDeclaration.parent?.parent;
340+
}
341+
342+
if (!namespace.length) {
343+
return undefined;
344+
} else {
345+
namespace.push(symbolName);
346+
return namespace.join(".");
347+
}
348+
}

0 commit comments

Comments
 (0)