Skip to content

Commit f653a79

Browse files
committed
Add extension support to ProjectGraph
1 parent 8b60050 commit f653a79

File tree

5 files changed

+255
-34
lines changed

5 files changed

+255
-34
lines changed

lib/graph/ProjectGraph.js

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,17 @@ class ProjectGraph {
1010
* @public
1111
* @param {object} parameters Parameters
1212
* @param {string} parameters.rootProjectName Root project name
13-
* @param {Array.<module:@ui5/project.specification.Extension>} parameters.extensions
14-
* Final list of extensions to be used in this project tree
1513
*/
16-
constructor({rootProjectName, extensions = []}) {
14+
constructor({rootProjectName}) {
1715
if (!rootProjectName) {
1816
throw new Error(`Could not create ProjectGraph: Missing or empty parameter 'rootProjectName'`);
1917
}
2018
this._rootProjectName = rootProjectName;
21-
this._extensions = Object.freeze(extensions);
2219

2320
this._projects = {}; // maps project name to instance
2421
this._adjList = {}; // maps project name to edges/dependencies
22+
23+
this._extensions = {}; // maps extension name to instance
2524
}
2625

2726
getRoot() {
@@ -32,14 +31,19 @@ class ProjectGraph {
3231
return rootProject;
3332
}
3433

34+
/**
35+
* @public
36+
* @param {module:@ui5/project.specification.Project} project Project which should be added to the graph
37+
* @param {boolean} [ignoreDuplicates=false] Whether an error should be thrown when a duplicate project is added
38+
*/
3539
addProject(project, ignoreDuplicates) {
3640
const projectName = project.getName();
3741
if (this._projects[projectName]) {
3842
if (ignoreDuplicates) {
3943
return;
4044
}
4145
throw new Error(
42-
`Failed to add project ${projectName} to the graph: A project with that name has already been added`);
46+
`Failed to add project ${projectName} to graph: A project with that name has already been added`);
4347
}
4448
if (!isNaN(projectName)) {
4549
// Reject integer-like project names. They would take precedence when traversing object keys which
@@ -51,10 +55,46 @@ class ProjectGraph {
5155
this._adjList[projectName] = {};
5256
}
5357

58+
/**
59+
* @public
60+
* @param {string} projectName Name of the project to retrieve
61+
* @returns {module:@ui5/project.specification.project|undefined}
62+
* project instance or undefined if the project is unknown to the graph
63+
*/
5464
getProject(projectName) {
5565
return this._projects[projectName];
5666
}
5767

68+
/**
69+
* @public
70+
* @param {module:@ui5/project.specification.Extension} extension Extension which should be available in the graph
71+
*/
72+
addExtension(extension) {
73+
const extensionName = extension.getName();
74+
if (this._extensions[extensionName]) {
75+
throw new Error(
76+
`Failed to add extension ${extensionName} to graph: ` +
77+
`An extension with that name has already been added`);
78+
}
79+
if (!isNaN(extensionName)) {
80+
// Reject integer-like extension names. They would take precedence when traversing object keys which
81+
// might lead to unexpected behavior in the future. We don't really expect anyone to use such names anyways
82+
throw new Error(
83+
`Failed to add extension ${extensionName} to graph: Extension name must not be integer-like`);
84+
}
85+
this._extensions[extensionName] = extension;
86+
}
87+
88+
/**
89+
* @public
90+
* @param {string} extensionName Name of the extension to retrieve
91+
* @returns {module:@ui5/project.specification.Extension|undefined}
92+
* Extension instance or undefined if the extension is unknown to the graph
93+
*/
94+
getExtension(extensionName) {
95+
return this._extensions[extensionName];
96+
}
97+
5898
/**
5999
* Declare a dependency from one project in the graph to another
60100
*

lib/graph/projectGraphFromTree.js

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,26 @@ const ProjectGraph = require("./ProjectGraph");
44
const ShimCollection = require("./ShimCollection");
55
const log = require("@ui5/logger").getLogger("graph:projectGraphFromTree");
66

7+
8+
function _handleExtensions(graph, shimCollection, extensions) {
9+
extensions.forEach((extension) => {
10+
const type = extension.getType();
11+
switch (type) {
12+
case "project-shim":
13+
shimCollection.addShim(extension);
14+
break;
15+
case "task":
16+
case "server-middleware":
17+
graph.addExtension(extension);
18+
break;
19+
default:
20+
throw new Error(
21+
`Encountered unexpected extension of type ${type} ` +
22+
`Supported types are 'project-shim', 'task' and 'middleware'`);
23+
}
24+
});
25+
}
26+
727
/**
828
* Tree node
929
*
@@ -22,15 +42,6 @@ const log = require("@ui5/logger").getLogger("graph:projectGraphFromTree");
2242
*/
2343
module.exports = async function(tree) {
2444
const shimCollection = new ShimCollection();
25-
26-
function addShimsToCollection(ext) {
27-
ext.forEach((e) => {
28-
if (e.getType() === "project-shim") {
29-
shimCollection.addShim(e);
30-
}
31-
});
32-
}
33-
3445
const moduleCollection = {};
3546

3647
const rootModule = new Module({
@@ -57,13 +68,18 @@ module.exports = async function(tree) {
5768
qualifiedApplicationProject = rootProject;
5869
}
5970

60-
addShimsToCollection(rootExtensions);
6171

6272
const projectGraph = new ProjectGraph({
6373
rootProjectName: rootProjectName
6474
});
6575
projectGraph.addProject(rootProject);
6676

77+
function handleExtensions(extensions) {
78+
return _handleExtensions(projectGraph, shimCollection, extensions);
79+
}
80+
81+
handleExtensions(rootExtensions);
82+
6783
const queue = [];
6884

6985
if (tree.dependencies) {
@@ -142,10 +158,8 @@ module.exports = async function(tree) {
142158
// Skip this node
143159
continue;
144160
}
145-
if (extensions.length) {
146-
addShimsToCollection(extensions);
147-
extensions.push(extensions);
148-
}
161+
162+
handleExtensions(extensions);
149163

150164
if (project) {
151165
const projectName = project.getName();

test/lib/extensions.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,6 @@ test("Project with unknown extension dependency inline configuration", (t) => {
583583
});
584584

585585
test("Project with task extension dependency", (t) => {
586-
// "project-type" extension handling not yet implemented => test currently checks for error
587586
const tree = {
588587
id: "application.a",
589588
path: applicationAPath,
@@ -617,7 +616,6 @@ test("Project with task extension dependency", (t) => {
617616
});
618617

619618
test("Project with task extension dependency - does not throw for invalid task path", async (t) => {
620-
// "project-type" extension handling not yet implemented => test currently checks for error
621619
const tree = {
622620
id: "application.a",
623621
path: applicationAPath,
@@ -648,7 +646,6 @@ test("Project with task extension dependency - does not throw for invalid task p
648646

649647

650648
test("Project with middleware extension dependency", (t) => {
651-
// "project-type" extension handling not yet implemented => test currently checks for error
652649
const tree = {
653650
id: "application.a",
654651
path: applicationAPath,
@@ -683,7 +680,6 @@ test("Project with middleware extension dependency", (t) => {
683680
});
684681

685682
test("Project with middleware extension dependency - middleware is missing configuration", async (t) => {
686-
// "project-type" extension handling not yet implemented => test currently checks for error
687683
const tree = {
688684
id: "application.a",
689685
path: applicationAPath,

test/lib/graph/ProjectGraph.js

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ const mock = require("mock-require");
44
const logger = require("@ui5/logger");
55
const Configuration = require("../../../lib/specifications/Configuration");
66
const Project = require("../../../lib/specifications/Project");
7-
// const Extension = require("../../../lib/specifications/Extension");
7+
const Extension = require("../../../lib/specifications/Extension");
88

99
function createProject(name) {
1010
const basicConfiguration = new Configuration({
1111
specVersion: "2.3",
1212
kind: "project",
13+
type: "application",
1314
metadata: {name}
1415
});
1516

@@ -20,6 +21,21 @@ function createProject(name) {
2021
configuration: basicConfiguration
2122
});
2223
}
24+
function createExtension(name) {
25+
const basicConfiguration = new Configuration({
26+
specVersion: "2.3",
27+
kind: "extension",
28+
type: "task",
29+
metadata: {name}
30+
});
31+
32+
return new Extension({
33+
id: "application.a.id",
34+
version: "1.0.0",
35+
modulePath: "some path",
36+
configuration: basicConfiguration
37+
});
38+
}
2339

2440
function traverseBreadthFirst(...args) {
2541
return _traverse(...args, true);
@@ -71,22 +87,15 @@ test("Instantiate a basic project graph", async (t) => {
7187
const {ProjectGraph} = t.context;
7288
t.notThrows(() => {
7389
new ProjectGraph({
74-
rootProjectName: "my root project",
75-
extensions: [
76-
"some extension"
77-
]
90+
rootProjectName: "my root project"
7891
});
7992
}, "Should not throw");
8093
});
8194

8295
test("Instantiate a basic project with missing parameter rootProjectName", async (t) => {
8396
const {ProjectGraph} = t.context;
8497
const error = t.throws(() => {
85-
new ProjectGraph({
86-
extensions: [
87-
"some extension"
88-
]
89-
});
98+
new ProjectGraph({});
9099
});
91100
t.is(error.message, "Could not create ProjectGraph: Missing or empty parameter 'rootProjectName'",
92101
"Should throw with expected error message");
@@ -141,7 +150,7 @@ test("addProject: Add duplicate", async (t) => {
141150
graph.addProject(project2);
142151
});
143152
t.is(error.message,
144-
"Failed to add project application.a to the graph: A project with that name has already been added",
153+
"Failed to add project application.a to graph: A project with that name has already been added",
145154
"Should throw with expected error message");
146155

147156
const res = graph.getProject("application.a");
@@ -189,6 +198,61 @@ test("getProject: Project is not in graph", async (t) => {
189198
t.is(res, undefined, "Should return undefined");
190199
});
191200

201+
test("add-/getExtension", async (t) => {
202+
const {ProjectGraph} = t.context;
203+
const graph = new ProjectGraph({
204+
rootProjectName: "my root project"
205+
});
206+
const extension = createExtension("extension.a");
207+
graph.addExtension(extension);
208+
const res = graph.getExtension("extension.a");
209+
t.is(res, extension, "Should return correct extension");
210+
});
211+
212+
test("addExtension: Add duplicate", async (t) => {
213+
const {ProjectGraph} = t.context;
214+
const graph = new ProjectGraph({
215+
rootProjectName: "my root project"
216+
});
217+
const extension1 = createExtension("extension.a");
218+
graph.addExtension(extension1);
219+
220+
const extension2 = createExtension("extension.a");
221+
const error = t.throws(() => {
222+
graph.addExtension(extension2);
223+
});
224+
t.is(error.message,
225+
"Failed to add extension extension.a to graph: An extension with that name has already been added",
226+
"Should throw with expected error message");
227+
228+
const res = graph.getExtension("extension.a");
229+
t.is(res, extension1, "Should return correct extension");
230+
});
231+
232+
test("addExtension: Add extension with integer-like name", async (t) => {
233+
const {ProjectGraph} = t.context;
234+
const graph = new ProjectGraph({
235+
rootProjectName: "my root project"
236+
});
237+
const extension = createExtension("1337");
238+
239+
const error = t.throws(() => {
240+
graph.addExtension(extension);
241+
});
242+
t.is(error.message,
243+
"Failed to add extension 1337 to graph: Extension name must not be integer-like",
244+
"Should throw with expected error message");
245+
});
246+
247+
test("getExtension: Project is not in graph", async (t) => {
248+
const {ProjectGraph} = t.context;
249+
const graph = new ProjectGraph({
250+
rootProjectName: "my root project"
251+
});
252+
const res = graph.getExtension("extension.a");
253+
t.is(res, undefined, "Should return undefined");
254+
});
255+
192256
test("declareDependency / getDependencies", async (t) => {
193257
const {ProjectGraph} = t.context;
194258
const graph = new ProjectGraph({

0 commit comments

Comments
 (0)