11const log = require ( "@ui5/logger" ) . getLogger ( "graph:ProjectGraph" ) ;
22/**
3- * A rooted, directed graph representing a UI5 project that should be worked with and all its dependencies
4- */
3+ * A rooted, directed graph representing a UI5 project and all its dependencies
4+ *
5+ * @public
6+ * @memberof module:@ui5/project.graph
7+ */
58class ProjectGraph {
69 /**
10+ * @public
711 * @param {object } parameters Parameters
812 * @param {string } parameters.rootProjectName Root project name
9- * @param {Array.<module:@ui5/project.Extension> } parameters.extensions
13+ * @param {Array.<module:@ui5/project.specification. Extension> } parameters.extensions
1014 * Final list of extensions to be used in this project tree
1115 */
1216 constructor ( { rootProjectName, extensions = [ ] } ) {
1317 if ( ! rootProjectName ) {
14- throw new Error ( `Could not create ProjectGraph: One or more required parameters are missing ` ) ;
18+ throw new Error ( `Could not create ProjectGraph: Missing or empty parameter 'rootProjectName' ` ) ;
1519 }
1620 this . _rootProjectName = rootProjectName ;
1721 this . _extensions = Object . freeze ( extensions ) ;
@@ -34,22 +38,30 @@ class ProjectGraph {
3438 if ( ignoreDuplicates ) {
3539 return ;
3640 }
37- throw new Error ( `Could not add duplicate project '${ projectName } ' to project tree` ) ;
41+ throw new Error (
42+ `Failed to add project ${ projectName } to the graph: A project with that name has already been added` ) ;
43+ }
44+ if ( ! isNaN ( projectName ) ) {
45+ // Reject integer-like project names. They would take precedence when traversing object keys which
46+ // could lead to unexpected behavior. We don't really expect anyone to use such names anyways
47+ throw new Error (
48+ `Failed to add project ${ projectName } to graph: Project name must not be integer-like` ) ;
3849 }
3950 this . _projects [ projectName ] = project ;
40- this . _adjList [ projectName ] = [ ] ;
51+ this . _adjList [ projectName ] = { } ;
4152 }
4253
4354 getProject ( projectName ) {
4455 return this . _projects [ projectName ] ;
4556 }
4657
4758 /**
48- * Declare a dependency from one project in the graph to another
49- *
50- * @param {string } fromProjectName Name of the depending project
51- * @param {string } toProjectName Name of project on which the other depends
52- */
59+ * Declare a dependency from one project in the graph to another
60+ *
61+ * @public
62+ * @param {string } fromProjectName Name of the depending project
63+ * @param {string } toProjectName Name of project on which the other depends
64+ */
5365 declareDependency ( fromProjectName , toProjectName /* , optional*/ ) {
5466 if ( ! this . _projects [ fromProjectName ] ) {
5567 throw new Error (
@@ -69,18 +81,49 @@ class ProjectGraph {
6981 }
7082 }
7183
84+
7285 /**
73- * Visit every project in the graph that can be reached by the given entry project exactly once.
74- * The entry project defaults to the root project.
75- * In case a cycle is detected, an error is thrown
76- *
77- * @param {Function } callback
78- */
79- async traverseBreadthFirst ( callback ) {
80- // TODO: Add parameter to define point of entry, defaulting to root
86+ * Callback for graph traversal operations
87+ *
88+ * @public
89+ * @async
90+ * @callback module:@ui 5/project.graph.ProjectGraph~traversalCallback
91+ * @param {object } parameters Parameters passed to the callback
92+ * @param {module:@ui5/project.specifications.Project } parameters.project The project that is currently visited
93+ * @param {module:@ui5/project.graph.ProjectGraph~getDependencies } parameters.getDependencies
94+ * Function to access the dependencies of the project that is currently visited.
95+ * @returns {Promise } Must return a promise on which the graph traversal will wait
96+ */
97+
98+ /**
99+ * Helper function available in the
100+ * traversalCallback]{@link module:@ui5/project.graph.ProjectGraph~traversalCallback} to access the
101+ * dependencies of the corresponding project in the current graph.
102+ * <br><br>
103+ * Note that transitive dependencies can't be accessed this way. Projects should rather add a direct
104+ * dependency to projects they need access to.
105+ *
106+ * @public
107+ * @function module:@ui 5/project.graph.ProjectGraph~getDependencies
108+ * @returns {Array.<module:@ui5/project.specifications.Project> } Direct dependencies of the visited project
109+ */
110+
111+ /**
112+ * Visit every project in the graph that can be reached by the given entry project exactly once.
113+ * The entry project defaults to the root project.
114+ * In case a cycle is detected, an error is thrown
115+ *
116+ * @public
117+ * @param {module:@ui5/project.graph.ProjectGraph~traversalCallback } callback Will be called
118+ * @param {string } [startName] Name of the project to start the traversal at. Defaults to the graph's root project
119+ */
120+ async traverseBreadthFirst ( callback , startName = this . _rootProjectName ) {
121+ if ( ! this . getProject ( startName ) ) {
122+ throw new Error ( `Failed to start graph traversal: Could not find project ${ startName } in graph` ) ;
123+ }
81124
82125 const queue = [ {
83- projectNames : [ this . _rootProjectName ] ,
126+ projectNames : [ startName ] ,
84127 predecessors : [ ]
85128 } ] ;
86129
@@ -114,7 +157,7 @@ class ProjectGraph {
114157 await callback ( {
115158 project : this . getProject ( projectName ) ,
116159 getDependencies : ( ) => {
117- return dependencies . map ( ( $ ) => this . getProject ( $ . projectName ) ) ;
160+ return dependencies . map ( ( $ ) => this . getProject ( $ ) ) ;
118161 }
119162 } ) ;
120163 } ) ( ) ;
@@ -123,15 +166,18 @@ class ProjectGraph {
123166 }
124167
125168 /**
126- * Visit every project in the graph that can be reached by the given entry project exactly once.
127- * The entry project defaults to the root project.
128- * In case a cycle is detected, an error is thrown
129- *
130- * @param {Function } callback
131- */
132- async traverseDepthFirst ( callback ) {
133- // TODO: Add parameter to define point of entry, defaulting to root
134- return this . _traverseDepthFirst ( this . _rootProjectName , { } , [ ] , callback ) ;
169+ * Visit every project in the graph that can be reached by the given entry project exactly once.
170+ * The entry project defaults to the root project.
171+ * In case a cycle is detected, an error is thrown
172+ *
173+ * @param {module:@ui5/project.graph.ProjectGraph~traversalCallback } callback Will be called
174+ * @param {string } [startName] Name of the project to start the traversal at. Defaults to the graph's root project
175+ */
176+ async traverseDepthFirst ( callback , startName = this . _rootProjectName ) {
177+ if ( ! this . getProject ( startName ) ) {
178+ throw new Error ( `Failed to start graph traversal: Could not find project ${ startName } in graph` ) ;
179+ }
180+ return this . _traverseDepthFirst ( startName , { } , [ ] , callback ) ;
135181 }
136182
137183 async _traverseDepthFirst ( projectName , visited , predecessors , callback ) {
@@ -156,7 +202,7 @@ class ProjectGraph {
156202 await callback ( {
157203 project : this . getProject ( projectName ) ,
158204 getDependencies : ( ) => {
159- return dependencies . map ( ( $ ) => this . getProject ( $ . projectName ) ) ;
205+ return dependencies . map ( ( $ ) => this . getProject ( $ ) ) ;
160206 }
161207 } ) ;
162208 } ) ( ) ;
0 commit comments