diff --git a/src/linter/linter.ts b/src/linter/linter.ts
index 0d3dd8b0..973afa6a 100644
--- a/src/linter/linter.ts
+++ b/src/linter/linter.ts
@@ -188,26 +188,38 @@ async function getProjectGraph(rootDir: string, ui5Config?: string | object): Pr
rootConfigPath = ui5YamlPath;
} else {
if (ui5Config) throw new Error(`Unable to find UI5 config file '${ui5Config}'`);
- const isApp = await dirExists(path.join(rootDir, "webapp"));
- if (isApp) {
- rootConfiguration = {
- specVersion: "3.0",
- type: "application",
- metadata: {
- name: "ui5-linter-target",
- },
- };
- } else {
- const isLibrary = await dirExists(path.join(rootDir, "src"));
- if (isLibrary) {
- rootConfiguration = {
- specVersion: "3.0",
- type: "library",
- metadata: {
- name: "ui5-linter-target",
- },
- };
- }
+
+ const dirChecks = await Promise.all([
+ dirExists(path.join(rootDir, "webapp")),
+ dirExists(path.join(rootDir, "src", "main", "webapp")),
+ dirExists(path.join(rootDir, "WebContent")),
+ dirExists(path.join(rootDir, "src", "main", "jslib")),
+ dirExists(path.join(rootDir, "src", "main", "js")),
+ dirExists(path.join(rootDir, "src", "main", "uilib")),
+ dirExists(path.join(rootDir, "src")),
+ ]);
+
+ if (dirChecks[0]) {
+ // Common app with webapp folder
+ rootConfiguration = createProjectConfig("application", "webapp");
+ } else if (dirChecks[1]) {
+ // Legacy app with src/main/webapp folder
+ rootConfiguration = createProjectConfig("application", "src/main/webapp");
+ } else if (dirChecks[2]) {
+ // Legacy app with WebContent folder
+ rootConfiguration = createProjectConfig("application", "WebContent");
+ } else if (dirChecks[3]) {
+ // Library with src/main/jslib folder
+ rootConfiguration = createProjectConfig("library", "src/main/jslib", "src/test/jslib");
+ } else if (dirChecks[4]) {
+ // Library with src/main/js folder
+ rootConfiguration = createProjectConfig("library", "src/main/js", "src/test/js");
+ } else if (dirChecks[5]) {
+ // Library with src/main/uilib folder
+ rootConfiguration = createProjectConfig("library", "src/main/uilib", "src/test/uilib");
+ } else if (dirChecks[6]) {
+ // Library with src folder
+ rootConfiguration = createProjectConfig("library", "src", "test");
}
}
@@ -231,6 +243,47 @@ async function getProjectGraph(rootDir: string, ui5Config?: string | object): Pr
});
}
+interface ProjectConfig {
+ specVersion: string;
+ type: string;
+ metadata: {
+ name: string;
+ };
+ resources?: {
+ configuration: {
+ paths: {
+ webapp?: string;
+ src?: string;
+ test?: string;
+ };
+ };
+ };
+}
+
+function createProjectConfig(projectType: string, projectSrcPath?: string, projectTestPath?: string): ProjectConfig {
+ let resourcesConfig: ProjectConfig["resources"] = {
+ configuration: {
+ paths: {},
+ },
+ };
+ if (projectType === "application") {
+ resourcesConfig.configuration.paths.webapp = projectSrcPath ?? "webapp";
+ } else if (projectType === "library") {
+ resourcesConfig.configuration.paths.src = projectSrcPath ?? "src";
+ resourcesConfig.configuration.paths.test = projectTestPath ?? "test";
+ } else {
+ // Do not set a resources configuration for other project types
+ resourcesConfig = undefined;
+ }
+ return {
+ specVersion: "4.0",
+ type: projectType,
+ metadata: {
+ name: "ui5-linter-project",
+ },
+ resources: resourcesConfig,
+ };
+}
/**
* Normalize provided virtual paths to the original file paths
*/
@@ -389,3 +442,8 @@ export function mergeIgnorePatterns(options: LinterOptions, config: UI5LintConfi
...(options.ignorePatterns ?? []), // CLI patterns go after config patterns
].filter(($) => $);
}
+
+// Export local function for testing only
+export const __localFunctions__ = (process.env.NODE_ENV === "test") ?
+ {getProjectGraph} :
+ /* istanbul ignore next */ undefined;
diff --git a/test/fixtures/linter/projects/legacy-dirs/legacy.app.a/WebContent/manifest.json b/test/fixtures/linter/projects/legacy-dirs/legacy.app.a/WebContent/manifest.json
new file mode 100644
index 00000000..3ff5a622
--- /dev/null
+++ b/test/fixtures/linter/projects/legacy-dirs/legacy.app.a/WebContent/manifest.json
@@ -0,0 +1,7 @@
+{
+ "_version": "1.12.0",
+ "sap.app": {
+ "id": "com.app",
+ "type": "application"
+ }
+}
diff --git a/test/fixtures/linter/projects/legacy-dirs/legacy.app.b/src/main/webapp/manifest.json b/test/fixtures/linter/projects/legacy-dirs/legacy.app.b/src/main/webapp/manifest.json
new file mode 100644
index 00000000..3ff5a622
--- /dev/null
+++ b/test/fixtures/linter/projects/legacy-dirs/legacy.app.b/src/main/webapp/manifest.json
@@ -0,0 +1,7 @@
+{
+ "_version": "1.12.0",
+ "sap.app": {
+ "id": "com.app",
+ "type": "application"
+ }
+}
diff --git a/test/fixtures/linter/projects/legacy-dirs/legacy.lib.a/src/main/jslib/.library b/test/fixtures/linter/projects/legacy-dirs/legacy.lib.a/src/main/jslib/.library
new file mode 100644
index 00000000..61286d35
--- /dev/null
+++ b/test/fixtures/linter/projects/legacy-dirs/legacy.lib.a/src/main/jslib/.library
@@ -0,0 +1,7 @@
+
+
+ library.with.custom.paths
+ SAP SE
+ ${version}
+ ${copyright}
+
diff --git a/test/fixtures/linter/projects/legacy-dirs/legacy.lib.b/src/main/uilib/.library b/test/fixtures/linter/projects/legacy-dirs/legacy.lib.b/src/main/uilib/.library
new file mode 100644
index 00000000..61286d35
--- /dev/null
+++ b/test/fixtures/linter/projects/legacy-dirs/legacy.lib.b/src/main/uilib/.library
@@ -0,0 +1,7 @@
+
+
+ library.with.custom.paths
+ SAP SE
+ ${version}
+ ${copyright}
+
diff --git a/test/fixtures/linter/projects/legacy-dirs/legacy.lib.c/src/main/js/.library b/test/fixtures/linter/projects/legacy-dirs/legacy.lib.c/src/main/js/.library
new file mode 100644
index 00000000..61286d35
--- /dev/null
+++ b/test/fixtures/linter/projects/legacy-dirs/legacy.lib.c/src/main/js/.library
@@ -0,0 +1,7 @@
+
+
+ library.with.custom.paths
+ SAP SE
+ ${version}
+ ${copyright}
+
diff --git a/test/lib/linter/linter.ts b/test/lib/linter/linter.ts
index dafbd754..1fd717b8 100644
--- a/test/lib/linter/linter.ts
+++ b/test/lib/linter/linter.ts
@@ -2,6 +2,7 @@ import anyTest, {TestFn} from "ava";
import sinonGlobal, {SinonStub} from "sinon";
import path from "node:path";
import {fileURLToPath} from "node:url";
+import esmock from "esmock";
import {
createTestsForFixtures, assertExpectedLintResults,
esmockDeprecationText, preprocessLintResultsForSnapshot,
@@ -28,7 +29,7 @@ test.after.always((t) => {
t.context.sinon.restore();
});
-// Define tests for reach file in the fixtures/linter/general directory
+// Define tests for each file in the fixtures/linter/general directory
createTestsForFixtures(fixturesGeneralPath);
// Test project fixtures individually
@@ -272,3 +273,154 @@ test.serial("lint: com.ui5.troublesome.app with custom UI5 config which does NOT
ui5Config,
}), {message: `Unable to find UI5 config file '${ui5Config}'`});
});
+
+test.serial("lint: getProjectGraph with different directory structures", async (t) => {
+ const graphFromObjectStub = t.context.sinon.stub().resolves();
+ const {__localFunctions__} = await esmock("../../../src/linter/linter.js", {
+ "@ui5/project/graph": {
+ graphFromObject: graphFromObjectStub,
+ },
+ });
+
+ const {getProjectGraph} = __localFunctions__;
+
+ const basePath = path.join(fixturesProjectsPath, "legacy-dirs");
+
+ // Legacy app structures
+ const appA = path.join(basePath, "legacy.app.a");
+ const appB = path.join(basePath, "legacy.app.b");
+ const libA = path.join(basePath, "legacy.lib.a");
+ const libB = path.join(basePath, "legacy.lib.b");
+ const libC = path.join(basePath, "legacy.lib.c");
+
+ await getProjectGraph(appA);
+ await getProjectGraph(appB);
+ await getProjectGraph(libA);
+ await getProjectGraph(libB);
+ await getProjectGraph(libC);
+
+ t.is(graphFromObjectStub.callCount, 5);
+ t.deepEqual(graphFromObjectStub.getCall(0).args[0], {
+ dependencyTree: {
+ dependencies: [],
+ id: "ui5-linter-target",
+ path: appA,
+ version: "1.0.0",
+ },
+ resolveFrameworkDependencies: false,
+ rootConfigPath: undefined,
+ rootConfiguration: {
+ metadata: {
+ name: "ui5-linter-project",
+ },
+ resources: {
+ configuration: {
+ paths: {
+ webapp: "WebContent",
+ },
+ },
+ },
+ specVersion: "4.0",
+ type: "application",
+ },
+ });
+ t.deepEqual(graphFromObjectStub.getCall(1).args[0], {
+ dependencyTree: {
+ dependencies: [],
+ id: "ui5-linter-target",
+ path: appB,
+ version: "1.0.0",
+ },
+ resolveFrameworkDependencies: false,
+ rootConfigPath: undefined,
+ rootConfiguration: {
+ metadata: {
+ name: "ui5-linter-project",
+ },
+ resources: {
+ configuration: {
+ paths: {
+ webapp: "src/main/webapp",
+ },
+ },
+ },
+ specVersion: "4.0",
+ type: "application",
+ },
+ });
+ t.deepEqual(graphFromObjectStub.getCall(2).args[0], {
+ dependencyTree: {
+ dependencies: [],
+ id: "ui5-linter-target",
+ path: libA,
+ version: "1.0.0",
+ },
+ resolveFrameworkDependencies: false,
+ rootConfigPath: undefined,
+ rootConfiguration: {
+ metadata: {
+ name: "ui5-linter-project",
+ },
+ resources: {
+ configuration: {
+ paths: {
+ src: "src/main/jslib",
+ test: "src/test/jslib",
+ },
+ },
+ },
+ specVersion: "4.0",
+ type: "library",
+ },
+ });
+ t.deepEqual(graphFromObjectStub.getCall(3).args[0], {
+ dependencyTree: {
+ dependencies: [],
+ id: "ui5-linter-target",
+ path: libB,
+ version: "1.0.0",
+ },
+ resolveFrameworkDependencies: false,
+ rootConfigPath: undefined,
+ rootConfiguration: {
+ metadata: {
+ name: "ui5-linter-project",
+ },
+ resources: {
+ configuration: {
+ paths: {
+ src: "src/main/uilib",
+ test: "src/test/uilib",
+ },
+ },
+ },
+ specVersion: "4.0",
+ type: "library",
+ },
+ });
+ t.deepEqual(graphFromObjectStub.getCall(4).args[0], {
+ dependencyTree: {
+ dependencies: [],
+ id: "ui5-linter-target",
+ path: libC,
+ version: "1.0.0",
+ },
+ resolveFrameworkDependencies: false,
+ rootConfigPath: undefined,
+ rootConfiguration: {
+ metadata: {
+ name: "ui5-linter-project",
+ },
+ resources: {
+ configuration: {
+ paths: {
+ src: "src/main/js",
+ test: "src/test/js",
+ },
+ },
+ },
+ specVersion: "4.0",
+ type: "library",
+ },
+ });
+});