Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: Load SAPUI5 types only when needed #46

Merged
merged 7 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions src/detectors/typeChecker/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,32 @@ async function collectTransitiveDependencies(pkgName: string, deps: Set<string>)
return deps;
}

async function collectSapui5TypesFiles() {
const typesDir = path.dirname(require.resolve("@sapui5/types/package.json"));
const allFiles = await fs.readdir(path.join(typesDir, "types"), {withFileTypes: true});
const typesFiles = [];
for (const entry of allFiles) {
if (entry.isFile() && entry.name.endsWith(".d.ts") && entry.name !== "index.d.ts") {
typesFiles.push(entry.name);
}
}
return typesFiles;
}

function addSapui5TypesMappingToCompilerOptions(sapui5TypesFiles: string[], options: ts.CompilerOptions) {
const paths = options.paths ?? (options.paths = {});
sapui5TypesFiles.forEach((fileName) => {
if (fileName === "sap.ui.core.d.ts") {
// No need to add a mapping for sap.ui.core, as it is loaded by default
return;
}
const libraryName = posixPath.basename(fileName, ".d.ts");
const namespace = libraryName.replace(/\./g, "/") + "/*";
const pathsEntry = paths[namespace] ?? (paths[namespace] = []);
pathsEntry.push(`/types/@sapui5/types/types/${fileName}`);
});
}

export async function createVirtualCompilerHost(
options: ts.CompilerOptions, files: Map<string, string>
): Promise<ts.CompilerHost> {
Expand All @@ -50,8 +76,15 @@ export async function createVirtualCompilerHost(
));

options.typeRoots = ["/types"];
// Request compiler to use all types we found in the dependencies of "@sapui5/types"
options.types = typePackageDirs;
options.types = [
// Request compiler to only use sap.ui.core types by default - other types will be loaded on demand
// (see addSapui5TypesMappingToCompilerOptions)
...typePackageDirs.filter((dir) => dir !== "/types/@sapui5/types/"),
"/types/@sapui5/types/types/sap.ui.core.d.ts",
];

// Adds mappings for all other sapui5 types, so that they are only loaded once a module is imported
addSapui5TypesMappingToCompilerOptions(await collectSapui5TypesFiles(), options);

// Create regex matching all path mapping keys
const pathMappingRegex = new RegExp(
Expand Down
9 changes: 9 additions & 0 deletions test/fixtures/linter/projects/sap.f/src/sap/f/.library
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" ?>
<library xmlns="http://www.sap.com/sap.ui.library.xsd">
<name>sap.f</name>
<dependencies>
<dependency>
<libraryName>sap.ui.core</libraryName>
</dependency>
</dependencies>
</library>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// This project is used to test the linter with the namespace of an OpenUI5 project.
9 changes: 9 additions & 0 deletions test/fixtures/linter/projects/sap.f/test/sap/f/LinterTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// This project is used to test the linter with the namespace of an OpenUI5 project.

sap.ui.require([
"sap/f/Avatar",
"sap/m/DateTimeInput"
], (Avatar, DateTimeInput) => {
new Avatar();
new DateTimeInput();
});
7 changes: 5 additions & 2 deletions test/fixtures/linter/rules/NoDeprecatedApi/NoDeprecatedApi.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
sap.ui.define([
"sap/m/Button", "sap/m/DateTimeInput", "sap/base/util/includes", "sap/ui/Device", "sap/ui/core/library",
"sap/m/Button", "sap/m/DateTimeInput", "sap/base/util/includes", "sap/ui/Device", "sap/ui/core/library", "sap/ui/generic/app/navigation/service/NavigationHandler",
"sap/ui/table/Table", "sap/ui/table/plugins/MultiSelectionPlugin", "sap/ui/core/Configuration", "sap/m/library"
], function(Button, DateTimeInput, includes, Device, coreLib, Table, MultiSelectionPlugin, Configuration, mobileLib) {
], function(Button, DateTimeInput, includes, Device, coreLib, NavigationHandler, Table, MultiSelectionPlugin, Configuration, mobileLib) {

var dateTimeInput = new DateTimeInput(); // TODO detect: Control is deprecated

Expand Down Expand Up @@ -34,4 +34,7 @@ sap.ui.define([
coreLib.MessageType; // Enum "MessageType" is deprecated

mobileLib.InputType.Date; // Enum value "InputType.Date" is deprecated

const navigationHandler = new NavigationHandler();
navigationHandler.storeInnerAppState({}); // Method "storeInnerAppState" is deprecated
});
18 changes: 18 additions & 0 deletions test/lib/linter/linter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,21 @@ test.serial("lint: All files of library.with.custom.paths", async (t) => {

t.snapshot(preprocessLintResultsForSnapshot(res));
});

test.serial("lint: All files of library with sap.f namespace", async (t) => {
const projectPath = path.join(fixturesProjectsPath, "sap.f");
const {lintProject} = t.context;

let res = await lintProject({
rootDir: projectPath,
filePaths: [],
reportCoverage: true,
messageDetails: true,
});

res = res.sort((a: {filePath: string}, b: {filePath: string}) => {
return a.filePath.localeCompare(b.filePath);
});

t.snapshot(preprocessLintResultsForSnapshot(res));
});
20 changes: 19 additions & 1 deletion test/lib/linter/rules/snapshots/NoDeprecatedApi.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Generated by [AVA](https://avajs.dev).
[
{
coverageInfo: [],
errorCount: 15,
errorCount: 17,
fatalErrorCount: 0,
filePath: 'NoDeprecatedApi.js',
messages: [
Expand All @@ -58,6 +58,15 @@ Generated by [AVA](https://avajs.dev).
ruleId: 'ui5-linter-no-deprecated-api',
severity: 2,
},
{
column: 107,
fatal: undefined,
line: 2,
message: 'Import of deprecated module \'sap/ui/generic/app/navigation/service/NavigationHandler\'',
messageDetails: 'Deprecated test message',
ruleId: 'ui5-linter-no-deprecated-api',
severity: 2,
},
{
column: 3,
fatal: undefined,
Expand Down Expand Up @@ -175,6 +184,15 @@ Generated by [AVA](https://avajs.dev).
ruleId: 'ui5-linter-no-deprecated-property',
severity: 2,
},
{
column: 2,
fatal: undefined,
line: 39,
message: 'Call to deprecated function \'storeInnerAppState\' of class \'NavigationHandler\'',
messageDetails: 'Deprecated test message',
ruleId: 'ui5-linter-no-deprecated-api',
severity: 2,
},
],
warningCount: 0,
},
Expand Down
Binary file modified test/lib/linter/rules/snapshots/NoDeprecatedApi.ts.snap
Binary file not shown.
42 changes: 42 additions & 0 deletions test/lib/linter/snapshots/linter.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -981,3 +981,45 @@ Generated by [AVA](https://avajs.dev).
warningCount: 0,
},
]

## lint: All files of library with sap.f namespace

> Snapshot 1

[
{
coverageInfo: [],
errorCount: 0,
fatalErrorCount: 0,
filePath: 'src/sap/f/LinterTest.js',
messages: [],
warningCount: 0,
},
{
coverageInfo: [],
errorCount: 2,
fatalErrorCount: 0,
filePath: 'test/sap/f/LinterTest.js',
messages: [
{
column: 2,
fatal: undefined,
line: 4,
message: 'Import of deprecated module \'sap/f/Avatar\'',
messageDetails: 'Deprecated test message',
ruleId: 'ui5-linter-no-deprecated-api',
severity: 2,
},
{
column: 2,
fatal: undefined,
line: 5,
message: 'Import of deprecated module \'sap/m/DateTimeInput\'',
messageDetails: 'Deprecated test message',
ruleId: 'ui5-linter-no-deprecated-api',
severity: 2,
},
],
warningCount: 0,
},
]
Binary file modified test/lib/linter/snapshots/linter.ts.snap
Binary file not shown.