diff --git a/src/build/helpers/BuildContext.ts b/src/build/helpers/BuildContext.ts
index 95c014ab2..49849b1f3 100644
--- a/src/build/helpers/BuildContext.ts
+++ b/src/build/helpers/BuildContext.ts
@@ -1,12 +1,25 @@
+import ProjectGraph from "../../graph/ProjectGraph.js";
import ProjectBuildContext from "./ProjectBuildContext.js";
import OutputStyleEnum from "./ProjectBuilderOutputStyle.js";
+interface BuildConfig {
+ selfContained: boolean;
+ cssVariables: boolean;
+ jsdoc: boolean;
+ createBuildManifest: boolean;
+ outputStyle: typeof OutputStyleEnum;
+ includedTasks: string[];
+ excludedTasks: string[];
+}
+
/**
* Context of a build process
*
*/
class BuildContext {
- constructor(graph, taskRepository, { // buildConfig
+ _graph: ProjectGraph;
+ _buildConfig: BuildConfig;
+ constructor(graph: ProjectGraph, taskRepository: typeof import("@ui5/builder/internal/taskRepository"), { // buildConfig
selfContained = false,
cssVariables = false,
jsdoc = false,
diff --git a/src/build/helpers/TaskUtil.ts b/src/build/helpers/TaskUtil.ts
index 44ffdce0f..076c07809 100644
--- a/src/build/helpers/TaskUtil.ts
+++ b/src/build/helpers/TaskUtil.ts
@@ -1,3 +1,4 @@
+import {ResourceInterface} from "@ui5/fs/Resource";
import {
createReaderCollection,
createReaderCollectionPrioritized,
@@ -16,7 +17,6 @@ import {
* The set of available functions on that interface depends on the specification
* version defined for the extension.
*
- * @alias @ui5/project/build/helpers/TaskUtil
* @hideconstructor
*/
class TaskUtil {
@@ -47,7 +47,9 @@ class TaskUtil {
* @param parameters
* @param parameters.projectBuildContext ProjectBuildContext
*/
- constructor({projectBuildContext}: object) {
+ STANDARD_TAGS: object;
+
+ constructor({projectBuildContext}) {
this._projectBuildContext = projectBuildContext;
/**
*/
@@ -79,7 +81,7 @@ class TaskUtil {
* [STANDARD_TAGS]{@link @ui5/project/build/helpers/TaskUtil#STANDARD_TAGS} are allowed
* @param [value] Tag value. Must be primitive
*/
- public setTag(resource, tag: string, value?: string | boolean | integer) {
+ public setTag(resource: ResourceInterface, tag: string, value?: string | boolean | number) {
if (typeof resource === "string") {
throw new Error("Deprecated parameter: " +
"Since UI5 Tooling 3.0, #setTag requires a resource instance. Strings are no longer accepted");
@@ -101,7 +103,7 @@ class TaskUtil {
* @returns Tag value for the given resource.
* undefined
if no value is available
*/
- public getTag(resource, tag: string) {
+ public getTag(resource: ResourceInterface, tag: string) {
if (typeof resource === "string") {
throw new Error("Deprecated parameter: " +
"Since UI5 Tooling 3.0, #getTag requires a resource instance. Strings are no longer accepted");
@@ -121,7 +123,7 @@ class TaskUtil {
* @param resource Resource-instance the tag should be cleared for
* @param tag Tag
*/
- public clearTag(resource, tag: string) {
+ public clearTag(resource: ResourceInterface, tag: string) {
if (typeof resource === "string") {
throw new Error("Deprecated parameter: " +
"Since UI5 Tooling 3.0, #clearTag requires a resource instance. Strings are no longer accepted");
diff --git a/src/build/helpers/createBuildManifest.ts b/src/build/helpers/createBuildManifest.ts
index 35a6324a4..c851da9b9 100644
--- a/src/build/helpers/createBuildManifest.ts
+++ b/src/build/helpers/createBuildManifest.ts
@@ -30,7 +30,7 @@ function getSortedTags(project) {
* @param buildConfig
* @param taskRepository
*/
-export default async function (project, buildConfig, taskRepository) {
+export default async function (project, buildConfig, taskRepository: typeof import("@ui5/builder/internal/taskRepository")) {
if (!project) {
throw new Error(`Missing parameter 'project'`);
}
diff --git a/src/graph/ProjectGraph.ts b/src/graph/ProjectGraph.ts
index 1dca99030..911753484 100644
--- a/src/graph/ProjectGraph.ts
+++ b/src/graph/ProjectGraph.ts
@@ -1,15 +1,32 @@
import OutputStyleEnum from "../build/helpers/ProjectBuilderOutputStyle.js";
import {getLogger} from "@ui5/logger";
+import type Project from "../specifications/Project.js";
+import type Extension from "../specifications/Extension.js";
+import type Specification from "../specifications/Specification.js";
+import type * as T_taskRepository from "@ui5/builder/internal/taskRepository";
const log = getLogger("graph:ProjectGraph");
+type TraversalCallback = (arg: {project: Project; dependencies: string[]}) => Promise;
+type VisitedNodes = Record | undefined>;
+
/**
* A rooted, directed graph representing a UI5 project, its dependencies and available extensions.
*
* While it allows defining cyclic dependencies, both traversal functions will throw an error if they encounter cycles.
*
- * @alias @ui5/project/graph/ProjectGraph
*/
class ProjectGraph {
+ _rootProjectName: string;
+
+ _projects: Map;
+ _adjList: Map>;
+ _optAdjList: Map>;
+ _extensions: Map;
+
+ _sealed: boolean;
+ _hasUnresolvedOptionalDependencies: boolean;
+ _taskRepository: typeof T_taskRepository | null;
+
/**
* @param parameters Parameters
* @param parameters.rootProjectName Root project name
@@ -38,7 +55,7 @@ class ProjectGraph {
*
* @returns Root project
*/
- public getRoot() {
+ public getRoot(): Specification {
const rootProject = this._projects.get(this._rootProjectName);
if (!rootProject) {
throw new Error(`Unable to find root project with name ${this._rootProjectName} in project graph`);
@@ -51,7 +68,7 @@ class ProjectGraph {
*
* @param project Project which should be added to the graph
*/
- public addProject(project) {
+ public addProject(project: Project) {
this._checkSealed();
const projectName = project.getName();
if (this._projects.has(projectName)) {
@@ -59,7 +76,7 @@ class ProjectGraph {
`Failed to add project ${projectName} to graph: A project with that name has already been added. ` +
`This might be caused by multiple modules containing projects with the same name`);
}
- if (!isNaN(projectName)) {
+ if (!isNaN(projectName as unknown as number)) {
// Reject integer-like project names. They would take precedence when traversing object keys which
// could lead to unexpected behavior. We don't really expect anyone to use such names anyways
throw new Error(
@@ -114,7 +131,7 @@ class ProjectGraph {
*
* @param extension Extension which should be available in the graph
*/
- public addExtension(extension) {
+ public addExtension(extension: Extension) {
this._checkSealed();
const extensionName = extension.getName();
if (this._extensions.has(extensionName)) {
@@ -123,7 +140,7 @@ class ProjectGraph {
`An extension with that name has already been added. ` +
`This might be caused by multiple modules containing extensions with the same name`);
}
- if (!isNaN(extensionName)) {
+ if (!isNaN(extensionName as unknown as number)) {
// Reject integer-like extension names. They would take precedence when traversing object keys which
// might lead to unexpected behavior in the future. We don't really expect anyone to use such names anyways
throw new Error(
@@ -171,9 +188,12 @@ class ProjectGraph {
log.verbose(`Declaring dependency: ${fromProjectName} depends on ${toProjectName}`);
this._declareDependency(this._adjList, fromProjectName, toProjectName);
} catch (err) {
- throw new Error(
- `Failed to declare dependency from project ${fromProjectName} to ${toProjectName}: ` +
- err.message);
+ if (err instanceof Error) {
+ throw new Error(
+ `Failed to declare dependency from project ${fromProjectName} to ${toProjectName}: ` +
+ err.message);
+ }
+ throw err;
}
}
@@ -190,9 +210,12 @@ class ProjectGraph {
this._declareDependency(this._optAdjList, fromProjectName, toProjectName);
this._hasUnresolvedOptionalDependencies = true;
} catch (err) {
- throw new Error(
- `Failed to declare optional dependency from project ${fromProjectName} to ${toProjectName}: ` +
- err.message);
+ if (err instanceof Error) {
+ throw new Error(
+ `Failed to declare optional dependency from project ${fromProjectName} to ${toProjectName}: ` +
+ err.message);
+ }
+ throw err;
}
}
@@ -203,7 +226,7 @@ class ProjectGraph {
* @param fromProjectName Name of the depending project
* @param toProjectName Name of project on which the other depends
*/
- _declareDependency(map: object, fromProjectName: string, toProjectName: string) {
+ _declareDependency(map: typeof this._adjList, fromProjectName: string, toProjectName: string) {
if (!this._projects.has(fromProjectName)) {
throw new Error(
`Unable to find depending project with name ${fromProjectName} in project graph`);
@@ -216,7 +239,7 @@ class ProjectGraph {
throw new Error(
`A project can't depend on itself`);
}
- const adjacencies = map.get(fromProjectName);
+ const adjacencies = map.get(fromProjectName)!;
if (adjacencies.has(toProjectName)) {
log.warn(`Dependency has already been declared: ${fromProjectName} depends on ${toProjectName}`);
} else {
@@ -254,8 +277,8 @@ class ProjectGraph {
`Unable to find project in project graph`);
}
- const processDependency = (depName) => {
- const adjacencies = this._adjList.get(depName);
+ const processDependency = (depName: string) => {
+ const adjacencies = this._adjList.get(depName)!;
adjacencies.forEach((depName) => {
if (!dependencies.has(depName)) {
dependencies.add(depName);
@@ -287,7 +310,7 @@ class ProjectGraph {
if (adjacencies.has(toProjectName)) {
return false;
}
- const optAdjacencies = this._optAdjList.get(fromProjectName);
+ const optAdjacencies = this._optAdjList.get(fromProjectName)!;
if (optAdjacencies.has(toProjectName)) {
return true;
}
@@ -340,26 +363,26 @@ class ProjectGraph {
}
}
- public async traverseBreadthFirst(startName?: string, callback) {
+ public async traverseBreadthFirst(startName?: string, callback?: TraversalCallback) {
if (!callback) {
// Default optional first parameter
- callback = startName;
+ callback = startName as unknown as TraversalCallback;
startName = this._rootProjectName;
}
- if (!this.getProject(startName)) {
+ if (!this.getProject(startName!)) {
throw new Error(`Failed to start graph traversal: Could not find project ${startName} in project graph`);
}
const queue = [{
- projectNames: [startName],
- ancestors: [],
+ projectNames: [startName] as string[],
+ ancestors: [] as string[],
}];
- const visited = Object.create(null);
+ const visited = Object.create(null) as VisitedNodes;
while (queue.length) {
- const {projectNames, ancestors} = queue.shift(); // Get and remove first entry from queue
+ const {projectNames, ancestors} = queue.shift()!; // Get and remove first entry from queue
await Promise.all(projectNames.map(async (projectName) => {
this._checkCycle(ancestors, projectName);
@@ -386,20 +409,22 @@ class ProjectGraph {
}
}
- public async traverseDepthFirst(startName?: string, callback) {
+ public async traverseDepthFirst(startName?: string, callback?: TraversalCallback) {
if (!callback) {
// Default optional first parameter
- callback = startName;
+ callback = startName as unknown as TraversalCallback;
startName = this._rootProjectName;
}
- if (!this.getProject(startName)) {
+ if (!this.getProject(startName!)) {
throw new Error(`Failed to start graph traversal: Could not find project ${startName} in project graph`);
}
- return this._traverseDepthFirst(startName, Object.create(null), [], callback);
+ return this._traverseDepthFirst(startName!, Object.create(null) as VisitedNodes, [], callback);
}
- async _traverseDepthFirst(projectName, visited, ancestors, callback) {
+ async _traverseDepthFirst(
+ projectName: string, visited: VisitedNodes, ancestors: string[], callback: TraversalCallback
+ ) {
this._checkCycle(ancestors, projectName);
if (visited[projectName]) {
@@ -425,7 +450,7 @@ class ProjectGraph {
*
* @param projectGraph Project Graph to merge into this one
*/
- public join(projectGraph) {
+ public join(projectGraph: ProjectGraph) {
try {
this._checkSealed();
if (!projectGraph.isSealed()) {
@@ -450,7 +475,7 @@ class ProjectGraph {
}
// Only to be used by @ui5/builder tests to inject its version of the taskRepository
- setTaskRepository(taskRepository) {
+ setTaskRepository(taskRepository: typeof T_taskRepository) {
this._taskRepository = taskRepository;
}
@@ -459,9 +484,12 @@ class ProjectGraph {
try {
this._taskRepository = await import("@ui5/builder/internal/taskRepository");
} catch (err) {
- throw new Error(
- `Failed to load task repository. Missing dependency to '@ui5/builder'? ` +
- `Error: ${err.message}`);
+ if (err instanceof Error) {
+ throw new Error(
+ `Failed to load task repository. Missing dependency to '@ui5/builder'? ` +
+ `Error: ${err.message}`);
+ }
+ throw err;
}
}
return this._taskRepository;
@@ -530,7 +558,7 @@ class ProjectGraph {
}
}
- _checkCycle(ancestors, projectName) {
+ _checkCycle(ancestors: string[], projectName: string) {
if (ancestors.includes(projectName)) {
// "Back-edge" detected. Neither BFS nor DFS searches should continue
// Mark first and last occurrence in chain with an asterisk and throw an error detailing the
@@ -543,12 +571,7 @@ class ProjectGraph {
// TODO: introduce function to check for dangling nodes/consistency in general?
}
-/**
- *
- * @param target
- * @param source
- */
-function mergeMap(target, source) {
+function mergeMap(target: Map>, source: Map>) {
for (const [key, value] of source) {
if (target.has(key)) {
throw new Error(`Failed to merge map: Key '${key}' already present in target set`);
diff --git a/src/specifications/Project.ts b/src/specifications/Project.ts
index cc46043a3..d1d74a399 100644
--- a/src/specifications/Project.ts
+++ b/src/specifications/Project.ts
@@ -4,7 +4,6 @@ import ResourceTagCollection from "@ui5/fs/internal/ResourceTagCollection";
/**
* Project
*
- * @alias @ui5/project/specifications/Project
* @hideconstructor
*/
class Project extends Specification {
diff --git a/src/specifications/Specification.ts b/src/specifications/Specification.ts
index 4a045fcf0..221a46422 100644
--- a/src/specifications/Specification.ts
+++ b/src/specifications/Specification.ts
@@ -1,21 +1,51 @@
import path from "node:path";
+import type Logger from "@ui5/logger/Logger";
import {getLogger} from "@ui5/logger";
import {createReader} from "@ui5/fs/resourceFactory";
import SpecificationVersion from "./SpecificationVersion.js";
+export interface SpecificationConfiguration {
+ kind: string;
+ type: string;
+ specVersion: string;
+ metadata: {
+ name: string;
+ };
+}
+
+interface LegacySpecificationConfiguration extends SpecificationConfiguration {
+ resources?: {
+ configuration?: {
+ propertiesFileSourceEncoding?: string;
+ };
+ };
+}
+
+interface SpecificationParameters {
+ id: string;
+ version: string;
+ modulePath: string;
+ configuration: SpecificationConfiguration;
+}
+
/**
* Abstract superclass for all projects and extensions
*
- * @alias @ui5/project/specifications/Specification
* @hideconstructor
*/
class Specification {
- public static async create(parameters: {
- id: string;
- version: string;
- modulePath: string;
- configuration: object;
- }) {
+ _log: Logger;
+ _version!: string;
+ _modulePath!: string;
+ __id!: string;
+ _name!: string;
+ _kind!: string;
+ _type!: string;
+ _specVersionString!: string;
+ _specVersion!: SpecificationVersion;
+ _config!: SpecificationConfiguration;
+
+ public static async create(parameters: SpecificationParameters) {
if (!parameters.configuration) {
throw new Error(
`Unable to create Specification instance: Missing configuration parameter`);
@@ -99,7 +129,7 @@ class Specification {
this.__id = id;
// Deep clone config to prevent changes by reference
- const config = JSON.parse(JSON.stringify(configuration));
+ const config = JSON.parse(JSON.stringify(configuration)) as SpecificationConfiguration;
const {validate} = await import("../validation/validator.js");
if (SpecificationVersion.major(config.specVersion) <= 1) {
@@ -107,7 +137,7 @@ class Specification {
this._log.verbose(`Detected legacy Specification Version ${config.specVersion}, defined for ` +
`${config.kind} ${config.metadata.name}. ` +
`Attempting to migrate the project to a supported specification version...`);
- this._migrateLegacyProject(config);
+ this._migrateLegacyProject(config as LegacySpecificationConfiguration);
try {
await validate({
config,
@@ -116,15 +146,18 @@ class Specification {
},
});
} catch (err) {
- this._log.verbose(
- `Validation error after migration of ${config.kind} ${config.metadata.name}:`);
- this._log.verbose(err.message);
- throw new Error(
- `${config.kind} ${config.metadata.name} defines unsupported Specification Version ` +
- `${originalSpecVersion}. Please manually upgrade to 3.0 or higher. ` +
- `For details see https://sap.github.io/ui5-tooling/pages/Configuration/#specification-versions - ` +
- `An attempted migration to a supported specification version failed, ` +
- `likely due to unrecognized configuration. Check verbose log for details.`);
+ if (err instanceof Error) {
+ this._log.verbose(
+ `Validation error after migration of ${config.kind} ${config.metadata.name}:`);
+ this._log.verbose(err.message);
+ throw new Error(
+ `${config.kind} ${config.metadata.name} defines unsupported Specification Version ` +
+ `${originalSpecVersion}. Please manually upgrade to 3.0 or higher. ` +
+ `For details see https://sap.github.io/ui5-tooling/pages/Configuration/#specification-versions - ` +
+ `An attempted migration to a supported specification version failed, ` +
+ `likely due to unrecognized configuration. Check verbose log for details.`);
+ }
+ throw err;
}
} else {
await validate({
@@ -235,7 +268,7 @@ class Specification {
* @returns Reader collection
*/
public getRootReader({useGitignore = true}: {
- useGitignore?: object;
+ useGitignore?: boolean;
} = {}) {
return createReader({
fsBasePath: this.getRootPath(),
@@ -247,13 +280,13 @@ class Specification {
private async _dirExists(dirPath: string) {
const resource = await this.getRootReader().byPath(dirPath, {nodir: false});
- if (resource && resource.getStatInfo().isDirectory()) {
+ if (resource?.getStatInfo().isDirectory()) {
return true;
}
return false;
}
- _migrateLegacyProject(config) {
+ _migrateLegacyProject(config: LegacySpecificationConfiguration) {
// Stick to 2.6 since 3.0 adds further restrictions (i.e. for the name) and enables
// functionality for extensions that shouldn't be enabled if the specVersion is not
// explicitly set to 3.x
@@ -264,20 +297,15 @@ class Specification {
// Adding back the old default if no configuration is provided.
if (config.kind === "project" && ["application", "library"].includes(config.type) &&
!config.resources?.configuration?.propertiesFileSourceEncoding) {
- config.resources = config.resources || {};
- config.resources.configuration = config.resources.configuration || {};
+ config.resources = config.resources ?? {};
+ config.resources.configuration = config.resources.configuration ?? {};
config.resources.configuration.propertiesFileSourceEncoding = "ISO-8859-1";
}
}
}
-/**
- *
- * @param moduleName
- * @param params
- */
-async function createAndInitializeSpec(moduleName, params) {
- const {default: Spec} = await import(`./${moduleName}`);
+async function createAndInitializeSpec(moduleName: string, params: SpecificationParameters) {
+ const {default: Spec} = await import(`./${moduleName}`) as {default: typeof Specification};
return new Spec().init(params);
}