Skip to content

Commit

Permalink
feat: Add UI5 linter option for ui5.yaml config path (#313)
Browse files Browse the repository at this point in the history
CPOUI5FOUNDATION-855
  • Loading branch information
maxreichmann authored Sep 20, 2024
1 parent 2342d34 commit a213084
Show file tree
Hide file tree
Showing 11 changed files with 303 additions and 41 deletions.
7 changes: 7 additions & 0 deletions src/cli/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface LinterArg {
details: boolean;
format: string;
config?: string;
ui5Config?: string;
}

// yargs type defition is missing the "middelwares" property for the CommandModule type
Expand Down Expand Up @@ -87,6 +88,10 @@ const lintCommand: FixedCommandModule<object, LinterArg> = {
type: "string",
choices: ["stylish", "json", "markdown"],
})
.option("ui5-config", {
describe: "Set a custom path for the UI5 Config (default: './ui5.yaml')",
type: "string",
})
.coerce([
// base.js
"log-level",
Expand Down Expand Up @@ -123,6 +128,7 @@ async function handleLint(argv: ArgumentsCamelCase<LinterArg>) {
details,
format,
config,
ui5Config,
} = argv;

let profile;
Expand All @@ -140,6 +146,7 @@ async function handleLint(argv: ArgumentsCamelCase<LinterArg>) {
reportCoverage,
includeMessageDetails: details,
configPath: config,
ui5ConfigPath: ui5Config,
});

if (reportCoverage) {
Expand Down
1 change: 1 addition & 0 deletions src/linter/LinterContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export interface LinterOptions {
reportCoverage?: boolean;
includeMessageDetails?: boolean;
configPath?: string;
ui5ConfigPath?: string;
}

export interface LinterParameters {
Expand Down
6 changes: 4 additions & 2 deletions src/linter/lintWorkspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import TypeLinter from "./ui5Types/TypeLinter.js";
import LinterContext, {LintResult, LinterParameters, LinterOptions} from "./LinterContext.js";
import {createReader} from "@ui5/fs/resourceFactory";
import {resolveIgnoresReader} from "./linter.js";
import {UI5LintConfigType} from "../utils/ConfigManager.js";

export default async function lintWorkspace(
workspace: AbstractAdapter, options: LinterOptions
workspace: AbstractAdapter, options: LinterOptions, config: UI5LintConfigType
): Promise<LintResult[]> {
const done = taskStart("Linting Workspace");

Expand All @@ -22,7 +23,8 @@ export default async function lintWorkspace(
createReader({
fsBasePath: options.rootDir,
virBasePath: "/",
})
}),
config
));

const params: LinterParameters = {
Expand Down
79 changes: 45 additions & 34 deletions src/linter/linter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,20 @@ import posixPath from "node:path/posix";
import {stat} from "node:fs/promises";
import {ProjectGraph} from "@ui5/project";
import type {AbstractReader, Resource} from "@ui5/fs";
import ConfigManager from "../utils/ConfigManager.js";
import ConfigManager, {UI5LintConfigType} from "../utils/ConfigManager.js";
import {Minimatch} from "minimatch";

async function lint(
resourceReader: AbstractReader, options: LinterOptions
): Promise<LintResult[]> {
const lintEnd = taskStart("Linting");
const {ignorePattern, configPath} = options;

const ignoresReader = await resolveIgnoresReader(
ignorePattern,
options.rootDir,
resourceReader,
configPath
);

const workspace = createWorkspace({
reader: ignoresReader,
});

const res = await lintWorkspace(workspace, options);
lintEnd();
return res;
}

export async function lintProject({
rootDir, pathsToLint, ignorePattern, reportCoverage, includeMessageDetails, configPath,
rootDir, pathsToLint, ignorePattern, reportCoverage, includeMessageDetails, configPath, ui5ConfigPath,
}: LinterOptions): Promise<LintResult[]> {
const configMngr = new ConfigManager(rootDir, configPath);
const config = await configMngr.getConfiguration();

// In case path is set both by CLI and config use CLI
ui5ConfigPath = ui5ConfigPath ?? config.ui5Config;

const projectGraphDone = taskStart("Project Graph creation");
const graph = await getProjectGraph(rootDir);
const graph = await getProjectGraph(rootDir, ui5ConfigPath);
const project = graph.getRoot();
projectGraphDone();

Expand Down Expand Up @@ -84,7 +68,8 @@ export async function lintProject({
reportCoverage,
includeMessageDetails,
configPath,
});
ui5ConfigPath,
}, config);

const relFsBasePath = path.relative(rootDir, fsBasePath);
const relFsBasePathTest = fsBasePathTest ? path.relative(rootDir, fsBasePathTest) : undefined;
Expand All @@ -102,6 +87,9 @@ export async function lintProject({
export async function lintFile({
rootDir, pathsToLint, ignorePattern, namespace, reportCoverage, includeMessageDetails, configPath,
}: LinterOptions): Promise<LintResult[]> {
const configMngr = new ConfigManager(rootDir, configPath);
const config = await configMngr.getConfiguration();

const reader = createReader({
fsBasePath: rootDir,
virBasePath: namespace ? `/resources/${namespace}/` : "/",
Expand All @@ -121,7 +109,7 @@ export async function lintFile({
reportCoverage,
includeMessageDetails,
configPath,
});
}, config);

res.forEach((result) => {
result.filePath = transformVirtualPathToFilePath(result.filePath, "", "/");
Expand All @@ -132,12 +120,36 @@ export async function lintFile({
return res;
}

async function getProjectGraph(rootDir: string): Promise<ProjectGraph> {
async function lint(
resourceReader: AbstractReader, options: LinterOptions, config: UI5LintConfigType
): Promise<LintResult[]> {
const lintEnd = taskStart("Linting");
const {ignorePattern, ui5ConfigPath} = options;

const ignoresReader = await resolveIgnoresReader(
ignorePattern,
options.rootDir,
resourceReader,
config,
ui5ConfigPath
);

const workspace = createWorkspace({
reader: ignoresReader,
});

const res = await lintWorkspace(workspace, options, config);
lintEnd();
return res;
}

async function getProjectGraph(rootDir: string, ui5ConfigPath?: string): Promise<ProjectGraph> {
let rootConfigPath, rootConfiguration;
const ui5YamlPath = path.join(rootDir, "ui5.yaml");
const ui5YamlPath = ui5ConfigPath ? path.join(rootDir, ui5ConfigPath) : path.join(rootDir, "ui5.yaml");
if (await fileExists(ui5YamlPath)) {
rootConfigPath = ui5YamlPath;
} else {
if (ui5ConfigPath) throw new Error(`Unable to find UI5 config file '${ui5ConfigPath}'`);
const isApp = await dirExists(path.join(rootDir, "webapp"));
if (isApp) {
rootConfiguration = {
Expand Down Expand Up @@ -309,13 +321,14 @@ export async function resolveIgnoresReader(
ignorePattern: string[] | undefined,
projectRootDir: string,
resourceReader: AbstractReader,
configPath?: string) {
config: UI5LintConfigType,
ui5ConfigPath?: string) {
let fsBasePath = "";
let fsBasePathTest = "";
let virBasePath = "/resources/";
let virBasePathTest = "/test-resources/";
try {
const graph = await getProjectGraph(projectRootDir);
const graph = await getProjectGraph(projectRootDir, ui5ConfigPath);
const project = graph.getRoot();
projectRootDir = project.getRootPath();
fsBasePath = project.getSourcePath();
Expand All @@ -334,11 +347,9 @@ export async function resolveIgnoresReader(
const relFsBasePath = path.relative(projectRootDir, fsBasePath);
const relFsBasePathTest = fsBasePathTest ? path.relative(projectRootDir, fsBasePathTest) : undefined;

const configMngr = new ConfigManager(projectRootDir, configPath);
const config = await configMngr.getConfiguration();
ignorePattern = [
...(config.ignores ?? []),
...(ignorePattern ?? []), // CLI patterns go after config patterns
...(ignorePattern ?? []), // patterns set by CLI go after config patterns
].filter(($) => $);

// Patterns must be only relative (to project's root),
Expand Down
1 change: 1 addition & 0 deletions src/utils/ConfigManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));

export interface UI5LintConfigType {
ignores?: string[];
ui5Config?: string;
};

const CONFIG_FILENAMES = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
specVersion: '4.0'
metadata:
name: com.ui5.troublesome.app
type: application
framework:
name: OpenUI5
version: "1.121.0"
libraries:
- name: sap.m
- name: sap.ui.core
- name: sap.landvisz
resources:
configuration:
paths:
webapp: webapp2
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
{
"_version": "1.12.0",

"sap.app": {
"id": "com.ui5.troublesome.app",
"type": "application",
"i18n": "i18n/i18n.properties",
"title": "{{appTitle}}",
"description": "{{appDescription}}",
"applicationVersion": {
"version": "1.0.0"
},
"dataSources": {
"v4": {
"uri": "/api/odata-4/",
"type": "OData",
"settings": {
"odataVersion": "4.0"
}
}
}
},

"sap.ui": {
"technology": "UI5",
"icons": {},
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": true
}
},

"sap.ui5": {
"rootView": {
"viewName": "com.ui5.troublesome.app.view.App",
"type": "XML",
"async": true,
"id": "app"
},

"dependencies": {
"minUI5Version": "1.119.0",
"libs": {
"sap.ui.core": {},
"sap.m": {},
"sap.ui.commons": {}
}
},

"handleValidation": true,

"contentDensities": {
"compact": true,
"cozy": true
},

"resources": {
"js": [{ "uri": "path/to/thirdparty.js" }]
},

"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "com.ui5.troublesome.app.i18n.i18n"
}
},
"odata-v4": {
"type": "sap.ui.model.odata.v4.ODataModel",
"settings": {
"synchronizationMode": "None"
}
},
"odata-v4-via-dataSource": {
"dataSource": "v4",
"settings": {
"synchronizationMode": "None"
}
},
"odata": {
"type": "sap.ui.model.odata.ODataModel",
"settings": {
"serviceUrl": "/api/odata"
}
}
},

"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "com.ui5.troublesome.app.view",
"controlId": "app",
"controlAggregation": "pages",
"async": true
},
"routes": [
{
"pattern": "",
"name": "main",
"target": "main"
}
],
"targets": {
"main": {
"viewId": "main",
"viewName": "Main"
}
}
}
}
}
Loading

0 comments on commit a213084

Please sign in to comment.