-
Notifications
You must be signed in to change notification settings - Fork 142
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: (strf-8608) update "tarjan-graph"
- Loading branch information
MaxGenash
committed
Sep 21, 2020
1 parent
7153455
commit 4a27ee6
Showing
8 changed files
with
235 additions
and
145 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
const Graph = require('tarjan-graph'); | ||
const util = require('util'); | ||
|
||
class Cycles { | ||
/** | ||
* @param {object[]} templatePaths | ||
*/ | ||
constructor(templatePaths) { | ||
if (!Array.isArray(templatePaths)) { | ||
throw new Error('templatePaths must be an Array'); | ||
} | ||
|
||
this.templatePaths = templatePaths; | ||
this.partialRegex = /\{\{>\s*([_|\-|a-zA-Z0-9\/]+)[^{]*?}}/g; | ||
this.dynamicComponentRegex = /\{\{\s*?dynamicComponent\s*(?:'|")([_|\-|a-zA-Z0-9\/]+)(?:'|").*?}}/g; | ||
} | ||
|
||
/** | ||
* Runs a graph based cyclical dependency check. Throws an error if circular dependencies are found | ||
* @returns {void} | ||
*/ | ||
detect() { | ||
for (const templatesByPath of this.templatePaths) { | ||
const graph = new Graph(); | ||
|
||
for (let [templatePath, templateContent] of Object.entries(templatesByPath)) { | ||
const dependencies = [ | ||
...this.geDependantPartials(templateContent, templatePath), | ||
...this.getDependantDynamicComponents(templateContent, templatesByPath, templatePath), | ||
]; | ||
|
||
graph.add(templatePath, dependencies); | ||
} | ||
|
||
if (graph.hasCycle()) { | ||
throw new Error('Circular dependency in template detected. \r\n' + util.inspect(graph.getCycles())); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @private | ||
* @param {string} templateContent | ||
* @param {string} pathToSkip | ||
* @returns {string[]} | ||
*/ | ||
geDependantPartials(templateContent, pathToSkip) { | ||
const dependencies = []; | ||
|
||
let match = this.partialRegex.exec(templateContent); | ||
while (match !== null) { | ||
const partialPath = match[1]; | ||
if (partialPath !== pathToSkip) { // skip the current templatePath | ||
dependencies.push(partialPath); | ||
} | ||
match = this.partialRegex.exec(templateContent); | ||
} | ||
|
||
return dependencies; | ||
} | ||
|
||
/** | ||
* @private | ||
* @param {string} templateContent | ||
* @param {object} allTemplatesByPath | ||
* @param {string} pathToSkip | ||
* @returns {string[]} | ||
*/ | ||
getDependantDynamicComponents(templateContent, allTemplatesByPath, pathToSkip) { | ||
const dependencies = []; | ||
|
||
let match = this.dynamicComponentRegex.exec(templateContent); | ||
while (match !== null) { | ||
const dynamicComponents = this.getDynamicComponents(match[1], allTemplatesByPath, pathToSkip); | ||
dependencies.push(...dynamicComponents); | ||
match = this.dynamicComponentRegex.exec(templateContent); | ||
} | ||
|
||
return dependencies; | ||
} | ||
|
||
/** | ||
* @private | ||
* @param {string} componentFolder | ||
* @param {object} possibleTemplates | ||
* @param {string} pathToSkip | ||
* @returns {string[]} | ||
*/ | ||
getDynamicComponents(componentFolder, possibleTemplates, pathToSkip) { | ||
return Object.keys(possibleTemplates).reduce((output, templatePath) => { | ||
if (templatePath.indexOf(componentFolder) === 0 && templatePath !== pathToSkip) { | ||
output.push(templatePath); | ||
} | ||
return output; | ||
}, []); | ||
} | ||
} | ||
|
||
module.exports = Cycles; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
const Cycles = require('./Cycles'); | ||
|
||
describe('Cycles', () => { | ||
const templatesWithCircles = [ | ||
{ | ||
"page":`--- | ||
front_matter_options: | ||
setting_x: | ||
value: {{theme_settings.front_matter_value}} | ||
--- | ||
<!DOCTYPE html> | ||
<html> | ||
<body> | ||
{{#if theme_settings.display_that}} | ||
<div>{{> components/index}}</div> | ||
{{/if}} | ||
</body> | ||
</html>`, | ||
"components/index":`<h1>Oh Hai there</h1> | ||
<p> | ||
<h1>Test product {{dynamicComponent 'components/options'}}</h1> | ||
</p>`, | ||
"components/options/date":`<h1>This is a dynamic component</h1> | ||
<h1>Test product {{> components/index}}</h1>`, | ||
}, | ||
{ | ||
"page2":`<!DOCTYPE html> | ||
<html> | ||
<body> | ||
<h1>{{theme_settings.customizable_title}}</h1> | ||
</body> | ||
</html>`, | ||
}, | ||
{ | ||
"components/index":`<h1>Oh Hai there</h1> | ||
<p> | ||
<h1>Test product {{dynamicComponent 'components/options'}}</h1> | ||
</p>`, | ||
"components/options/date":`<h1>This is a dynamic component</h1> | ||
<h1>Test product {{> components/index}}</h1>`, | ||
}, | ||
{ | ||
"components/options/date":`<h1>This is a dynamic component</h1> | ||
<h1>Test product {{> components/index}}</h1>`, | ||
"components/index":`<h1>Oh Hai there</h1> | ||
<p> | ||
<h1>Test product {{dynamicComponent 'components/options'}}</h1> | ||
</p>`, | ||
}, | ||
]; | ||
|
||
const templatesWithoutCircles = [ | ||
{ | ||
"page":`--- | ||
front_matter_options: | ||
setting_x: | ||
value: {{theme_settings.front_matter_value}} | ||
--- | ||
<!DOCTYPE html> | ||
<html> | ||
<body> | ||
{{#if theme_settings.display_that}} | ||
<div>{{> components/index}}</div> | ||
{{/if}} | ||
</body> | ||
</html>`, | ||
"components/index": `<h1>This is the index</h1>`, | ||
}, | ||
{ | ||
"page2":`<!DOCTYPE html> | ||
<html> | ||
<body> | ||
<h1>{{theme_settings.customizable_title}}</h1> | ||
</body> | ||
</html>`, | ||
}, | ||
]; | ||
|
||
const templatesWithSelfReferences = [ | ||
{ | ||
"page":`--- | ||
front_matter_options: | ||
setting_x: | ||
value: {{theme_settings.front_matter_value}} | ||
--- | ||
<!DOCTYPE html> | ||
<html> | ||
<body> | ||
{{#if theme_settings.display_that}} | ||
<div>{{> components/index}}</div> | ||
{{/if}} | ||
<h1>Self-reference: {{dynamicComponent 'page'}}</h1> | ||
</body> | ||
</html>`, | ||
"components/index": `<h1>This is the index</h1>`, | ||
}, | ||
]; | ||
|
||
it('should throw error when cycle is detected', () => { | ||
const action = () => { | ||
new Cycles(templatesWithCircles).detect(); | ||
}; | ||
|
||
expect(action).toThrow(Error, /Circular/); | ||
}); | ||
|
||
it('should throw an error when non array passed in', () => { | ||
const action = () => { | ||
new Cycles('test'); | ||
}; | ||
|
||
expect(action).toThrow(Error); | ||
}); | ||
|
||
it('should not throw an error when cycles weren\'t detected', () => { | ||
const action = () => { | ||
new Cycles(templatesWithoutCircles).detect(); | ||
}; | ||
|
||
expect(action).not.toThrow(); | ||
}); | ||
|
||
it('should not throw an error for self-references', () => { | ||
const action = () => { | ||
new Cycles(templatesWithSelfReferences).detect(); | ||
}; | ||
|
||
expect(action).not.toThrow(); | ||
}); | ||
}); |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.