Skip to content

Commit

Permalink
feat(linter): Extend project initialization fallbacks
Browse files Browse the repository at this point in the history
Enhance compatibility for legacy UI5 projects that do not include a
ui5.yaml and do not use current best practice directory structures like
'webapp' for apps and 'src'/'test' for libraries.

In addition to those directories we now also check and eventually use
the following directories:

Applications:
* src/main/webapp
* WebContent

Libraries:
* src/main/jslib (+ src/test/jslib)
* src/main/uilib (+ src/test/uilib)
* src/main/js (+ src/test/js)
  • Loading branch information
RandomByte committed Dec 17, 2024
1 parent 702c258 commit f9b0f96
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 21 deletions.
98 changes: 78 additions & 20 deletions src/linter/linter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}

Expand All @@ -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
*/
Expand Down Expand Up @@ -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;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"_version": "1.12.0",
"sap.app": {
"id": "com.app",
"type": "application"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"_version": "1.12.0",
"sap.app": {
"id": "com.app",
"type": "application"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" ?>
<library xmlns="http://www.sap.com/sap.ui.library.xsd">
<name>library.with.custom.paths</name>
<vendor>SAP SE</vendor>
<version>${version}</version>
<copyright>${copyright}</copyright>
</library>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" ?>
<library xmlns="http://www.sap.com/sap.ui.library.xsd">
<name>library.with.custom.paths</name>
<vendor>SAP SE</vendor>
<version>${version}</version>
<copyright>${copyright}</copyright>
</library>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" ?>
<library xmlns="http://www.sap.com/sap.ui.library.xsd">
<name>library.with.custom.paths</name>
<vendor>SAP SE</vendor>
<version>${version}</version>
<copyright>${copyright}</copyright>
</library>
154 changes: 153 additions & 1 deletion test/lib/linter/linter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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",
},
});
});

0 comments on commit f9b0f96

Please sign in to comment.