From 518afbbcd66e0e85c020de50e7f690d3f847a343 Mon Sep 17 00:00:00 2001 From: Koza Date: Wed, 10 Nov 2021 18:00:26 +0100 Subject: [PATCH 1/2] Absorb sass-graph 2.2.5 into node-sass --- lib/sass-graph/parse-imports.js | 64 +++++++ lib/sass-graph/sass-graph.js | 160 +++++++++++++++++ lib/watcher.js | 2 +- package.json | 2 +- .../_nested.scss/_leaf.scss | 0 .../fixtures/folder-with-extension/index.scss | 4 + .../fixtures/indented-syntax/_c.sass | 1 + .../fixtures/indented-syntax/b.sass | 1 + .../fixtures/indented-syntax/index.sass | 1 + .../fixtures/indented-syntax/nested/_d.sass | 1 + .../fixtures/indented-syntax/nested/_e.sass | 0 .../sass-graph/fixtures/load-path-cwd/_b.scss | 1 + .../fixtures/load-path-cwd/index.scss | 1 + .../load-path-cwd/inside-load-path/_b.scss | 2 + .../load-path-cwd/inside-load-path/_c.scss | 2 + .../load-path-cwd/outside-load-path/_b.scss | 2 + .../load-path-cwd/outside-load-path/_c.scss | 2 + test/sass-graph/fixtures/load-path/_d.scss | 0 test/sass-graph/fixtures/load-path/index.scss | 1 + .../load-path/inside-load-path/_b.scss | 1 + .../load-path/inside-load-path/_c.scss | 1 + .../load-path/outside-load-path/_b.scss | 2 + .../load-path/outside-load-path/_c.scss | 2 + .../fixtures/mutliple-ancestors/_leaf.scss | 0 .../fixtures/mutliple-ancestors/entry_a.scss | 1 + .../fixtures/mutliple-ancestors/entry_b.scss | 1 + .../fixtures/mutliple-ancestors/entry_c.scss | 1 + .../fixtures/mutliple-ancestors/entry_d.scss | 1 + .../sass-graph/fixtures/no-imports/index.scss | 4 + test/sass-graph/fixtures/simple/_c.scss | 1 + test/sass-graph/fixtures/simple/b.scss | 1 + test/sass-graph/fixtures/simple/index.scss | 1 + .../sass-graph/fixtures/simple/nested/_d.scss | 1 + .../sass-graph/fixtures/simple/nested/_e.scss | 0 test/sass-graph/parse-directory.js | 30 ++++ test/sass-graph/parse-file.js | 164 ++++++++++++++++++ test/sass-graph/parse-imports.js | 155 +++++++++++++++++ test/sass-graph/util.js | 68 ++++++++ 38 files changed, 680 insertions(+), 2 deletions(-) create mode 100644 lib/sass-graph/parse-imports.js create mode 100644 lib/sass-graph/sass-graph.js create mode 100644 test/sass-graph/fixtures/folder-with-extension/_nested.scss/_leaf.scss create mode 100644 test/sass-graph/fixtures/folder-with-extension/index.scss create mode 100644 test/sass-graph/fixtures/indented-syntax/_c.sass create mode 100644 test/sass-graph/fixtures/indented-syntax/b.sass create mode 100644 test/sass-graph/fixtures/indented-syntax/index.sass create mode 100644 test/sass-graph/fixtures/indented-syntax/nested/_d.sass create mode 100644 test/sass-graph/fixtures/indented-syntax/nested/_e.sass create mode 100644 test/sass-graph/fixtures/load-path-cwd/_b.scss create mode 100644 test/sass-graph/fixtures/load-path-cwd/index.scss create mode 100644 test/sass-graph/fixtures/load-path-cwd/inside-load-path/_b.scss create mode 100644 test/sass-graph/fixtures/load-path-cwd/inside-load-path/_c.scss create mode 100644 test/sass-graph/fixtures/load-path-cwd/outside-load-path/_b.scss create mode 100644 test/sass-graph/fixtures/load-path-cwd/outside-load-path/_c.scss create mode 100644 test/sass-graph/fixtures/load-path/_d.scss create mode 100644 test/sass-graph/fixtures/load-path/index.scss create mode 100644 test/sass-graph/fixtures/load-path/inside-load-path/_b.scss create mode 100644 test/sass-graph/fixtures/load-path/inside-load-path/_c.scss create mode 100644 test/sass-graph/fixtures/load-path/outside-load-path/_b.scss create mode 100644 test/sass-graph/fixtures/load-path/outside-load-path/_c.scss create mode 100644 test/sass-graph/fixtures/mutliple-ancestors/_leaf.scss create mode 100644 test/sass-graph/fixtures/mutliple-ancestors/entry_a.scss create mode 100644 test/sass-graph/fixtures/mutliple-ancestors/entry_b.scss create mode 100644 test/sass-graph/fixtures/mutliple-ancestors/entry_c.scss create mode 100644 test/sass-graph/fixtures/mutliple-ancestors/entry_d.scss create mode 100644 test/sass-graph/fixtures/no-imports/index.scss create mode 100644 test/sass-graph/fixtures/simple/_c.scss create mode 100644 test/sass-graph/fixtures/simple/b.scss create mode 100644 test/sass-graph/fixtures/simple/index.scss create mode 100644 test/sass-graph/fixtures/simple/nested/_d.scss create mode 100644 test/sass-graph/fixtures/simple/nested/_e.scss create mode 100644 test/sass-graph/parse-directory.js create mode 100644 test/sass-graph/parse-file.js create mode 100644 test/sass-graph/parse-imports.js create mode 100644 test/sass-graph/util.js diff --git a/lib/sass-graph/parse-imports.js b/lib/sass-graph/parse-imports.js new file mode 100644 index 000000000..634253ec4 --- /dev/null +++ b/lib/sass-graph/parse-imports.js @@ -0,0 +1,64 @@ +var tokenizer = require('scss-tokenizer'); + +function parseImports(content, isIndentedSyntax) { + var tokens = tokenizer.tokenize(content); + var results = []; + var tmp = ''; + var inImport = false; + var inParen = false; + var prevToken = tokens[0]; + + var i, token; + for (i = 1; i < tokens.length; i++) { + token = tokens[i]; + + if (inImport && !inParen && token[0] === 'string') { + results.push(token[1]); + } + else if (token[1] === 'import' && prevToken[1] === '@') { + if (inImport && !isIndentedSyntax) { + throw new Error('Encountered invalid @import syntax.'); + } + + inImport = true; + } + else if (inImport && !inParen && (token[0] === 'ident' || token[0] === '/')) { + tmp += token[1]; + } + else if (inImport && !inParen && (token[0] === 'space' || token[0] === 'newline')) { + if (tmp !== '') { + results.push(tmp); + tmp = ''; + + if (isIndentedSyntax) { + inImport = false; + } + } + } + else if (inImport && token[0] === ';') { + inImport = false; + + if (tmp !== '') { + results.push(tmp); + tmp = ''; + } + } + else if (inImport && token[0] === '(') { + inParen = true; + tmp = ''; + } + else if (inParen && token[0] === ')') { + inParen = false; + } + + prevToken = token; + } + + if (tmp !== '') { + results.push(tmp); + } + + return results; +} + +module.exports = parseImports; diff --git a/lib/sass-graph/sass-graph.js b/lib/sass-graph/sass-graph.js new file mode 100644 index 000000000..5d5aba0ef --- /dev/null +++ b/lib/sass-graph/sass-graph.js @@ -0,0 +1,160 @@ +'use strict'; + +var fs = require('fs'); +var path = require('path'); +var _ = require('lodash'); +var glob = require('glob'); +var parseImports = require('./parse-imports'); + +// resolve a sass module to a path +function resolveSassPath(sassPath, loadPaths, extensions) { + // trim sass file extensions + var re = new RegExp('(\.('+extensions.join('|')+'))$', 'i'); + var sassPathName = sassPath.replace(re, ''); + // check all load paths + var i, j, length = loadPaths.length, scssPath, partialPath; + for (i = 0; i < length; i++) { + for (j = 0; j < extensions.length; j++) { + scssPath = path.normalize(loadPaths[i] + '/' + sassPathName + '.' + extensions[j]); + try { + if (fs.lstatSync(scssPath).isFile()) { + return scssPath; + } + } catch (e) {} + } + + // special case for _partials + for (j = 0; j < extensions.length; j++) { + scssPath = path.normalize(loadPaths[i] + '/' + sassPathName + '.' + extensions[j]); + partialPath = path.join(path.dirname(scssPath), '_' + path.basename(scssPath)); + try { + if (fs.lstatSync(partialPath).isFile()) { + return partialPath; + } + } catch (e) {} + } + } + + // File to import not found or unreadable so we assume this is a custom import + return false; +} + +function Graph(options, dir) { + this.dir = dir; + this.extensions = options.extensions || []; + this.index = {}; + this.follow = options.follow || false; + this.loadPaths = _(options.loadPaths).map(function(p) { + return path.resolve(p); + }).value(); + + if (dir) { + var graph = this; + _.each(glob.sync(dir+'/**/*.@('+this.extensions.join('|')+')', { dot: true, nodir: true, follow: this.follow }), function(file) { + graph.addFile(path.resolve(file)); + }); + } +} + +// add a sass file to the graph +Graph.prototype.addFile = function(filepath, parent) { + var entry = this.index[filepath] = this.index[filepath] || { + imports: [], + importedBy: [], + modified: fs.statSync(filepath).mtime + }; + + var resolvedParent; + var isIndentedSyntax = path.extname(filepath) === '.sass'; + var imports = parseImports(fs.readFileSync(filepath, 'utf-8'), isIndentedSyntax); + var cwd = path.dirname(filepath); + + var i, length = imports.length, loadPaths, resolved; + for (i = 0; i < length; i++) { + loadPaths = _([cwd, this.dir]).concat(this.loadPaths).filter().uniq().value(); + resolved = resolveSassPath(imports[i], loadPaths, this.extensions); + if (!resolved) continue; + + // recurse into dependencies if not already enumerated + if (!_.includes(entry.imports, resolved)) { + entry.imports.push(resolved); + this.addFile(fs.realpathSync(resolved), filepath); + } + } + + // add link back to parent + if (parent) { + resolvedParent = _(parent).intersection(this.loadPaths).value(); + + if (resolvedParent) { + resolvedParent = parent.substr(parent.indexOf(resolvedParent)); + } else { + resolvedParent = parent; + } + + entry.importedBy.push(resolvedParent); + } +}; + +// visits all files that are ancestors of the provided file +Graph.prototype.visitAncestors = function(filepath, callback) { + this.visit(filepath, callback, function(err, node) { + if (err || !node) return []; + return node.importedBy; + }); +}; + +// visits all files that are descendents of the provided file +Graph.prototype.visitDescendents = function(filepath, callback) { + this.visit(filepath, callback, function(err, node) { + if (err || !node) return []; + return node.imports; + }); +}; + +// a generic visitor that uses an edgeCallback to find the edges to traverse for a node +Graph.prototype.visit = function(filepath, callback, edgeCallback, visited) { + filepath = fs.realpathSync(filepath); + var visited = visited || []; + if (!this.index.hasOwnProperty(filepath)) { + edgeCallback('Graph doesn\'t contain ' + filepath, null); + } + var edges = edgeCallback(null, this.index[filepath]); + + var i, length = edges.length; + for (i = 0; i < length; i++) { + if (!_.includes(visited, edges[i])) { + visited.push(edges[i]); + callback(edges[i], this.index[edges[i]]); + this.visit(edges[i], callback, edgeCallback, visited); + } + } +}; + +function processOptions(options) { + return _.assign({ + loadPaths: [process.cwd()], + extensions: ['scss', 'css', 'sass'], + }, options); +} + +module.exports.parseFile = function(filepath, options) { + if (fs.lstatSync(filepath).isFile()) { + filepath = path.resolve(filepath); + options = processOptions(options); + var graph = new Graph(options); + graph.addFile(filepath); + return graph; + } + // throws +}; + +module.exports.parseDir = function(dirpath, options) { + if (fs.lstatSync(dirpath).isDirectory()) { + dirpath = path.resolve(dirpath); + options = processOptions(options); + var graph = new Graph(options, dirpath); + return graph; + } + // throws +}; diff --git a/lib/watcher.js b/lib/watcher.js index 89443b415..8d813ccfd 100644 --- a/lib/watcher.js +++ b/lib/watcher.js @@ -1,4 +1,4 @@ -var grapher = require('sass-graph'), +var grapher = require('./sass-graph/sass-graph'), clonedeep = require('lodash/cloneDeep'), path = require('path'), config = {}, diff --git a/package.json b/package.json index f6297a1a8..f9febd03b 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "node-gyp": "^7.1.0", "npmlog": "^5.0.0", "request": "^2.88.0", - "sass-graph": "2.2.5", + "scss-tokenizer": "^0.2.3", "stdout-stream": "^1.4.0", "true-case-path": "^1.0.2" }, diff --git a/test/sass-graph/fixtures/folder-with-extension/_nested.scss/_leaf.scss b/test/sass-graph/fixtures/folder-with-extension/_nested.scss/_leaf.scss new file mode 100644 index 000000000..e69de29bb diff --git a/test/sass-graph/fixtures/folder-with-extension/index.scss b/test/sass-graph/fixtures/folder-with-extension/index.scss new file mode 100644 index 000000000..cef729717 --- /dev/null +++ b/test/sass-graph/fixtures/folder-with-extension/index.scss @@ -0,0 +1,4 @@ +@import 'nested.scss'; +@import '_nested.scss'; +@import 'nested.scss/leaf'; +@import '_nested.scss/leaf'; diff --git a/test/sass-graph/fixtures/indented-syntax/_c.sass b/test/sass-graph/fixtures/indented-syntax/_c.sass new file mode 100644 index 000000000..feb52d18c --- /dev/null +++ b/test/sass-graph/fixtures/indented-syntax/_c.sass @@ -0,0 +1 @@ +@import nested/d diff --git a/test/sass-graph/fixtures/indented-syntax/b.sass b/test/sass-graph/fixtures/indented-syntax/b.sass new file mode 100644 index 000000000..f0caa1a19 --- /dev/null +++ b/test/sass-graph/fixtures/indented-syntax/b.sass @@ -0,0 +1 @@ +@import c.sass diff --git a/test/sass-graph/fixtures/indented-syntax/index.sass b/test/sass-graph/fixtures/indented-syntax/index.sass new file mode 100644 index 000000000..6eeb29bea --- /dev/null +++ b/test/sass-graph/fixtures/indented-syntax/index.sass @@ -0,0 +1 @@ +@import b diff --git a/test/sass-graph/fixtures/indented-syntax/nested/_d.sass b/test/sass-graph/fixtures/indented-syntax/nested/_d.sass new file mode 100644 index 000000000..04860b699 --- /dev/null +++ b/test/sass-graph/fixtures/indented-syntax/nested/_d.sass @@ -0,0 +1 @@ +@import e diff --git a/test/sass-graph/fixtures/indented-syntax/nested/_e.sass b/test/sass-graph/fixtures/indented-syntax/nested/_e.sass new file mode 100644 index 000000000..e69de29bb diff --git a/test/sass-graph/fixtures/load-path-cwd/_b.scss b/test/sass-graph/fixtures/load-path-cwd/_b.scss new file mode 100644 index 000000000..1730d60a6 --- /dev/null +++ b/test/sass-graph/fixtures/load-path-cwd/_b.scss @@ -0,0 +1 @@ +@import 'c'; diff --git a/test/sass-graph/fixtures/load-path-cwd/index.scss b/test/sass-graph/fixtures/load-path-cwd/index.scss new file mode 100644 index 000000000..7b7f19389 --- /dev/null +++ b/test/sass-graph/fixtures/load-path-cwd/index.scss @@ -0,0 +1 @@ +@import "b"; diff --git a/test/sass-graph/fixtures/load-path-cwd/inside-load-path/_b.scss b/test/sass-graph/fixtures/load-path-cwd/inside-load-path/_b.scss new file mode 100644 index 000000000..cc7403e9d --- /dev/null +++ b/test/sass-graph/fixtures/load-path-cwd/inside-load-path/_b.scss @@ -0,0 +1,2 @@ +// Should not be loaded +@import 'c'; diff --git a/test/sass-graph/fixtures/load-path-cwd/inside-load-path/_c.scss b/test/sass-graph/fixtures/load-path-cwd/inside-load-path/_c.scss new file mode 100644 index 000000000..dd57526fc --- /dev/null +++ b/test/sass-graph/fixtures/load-path-cwd/inside-load-path/_c.scss @@ -0,0 +1,2 @@ +// Should not be loaded +@import 'd'; diff --git a/test/sass-graph/fixtures/load-path-cwd/outside-load-path/_b.scss b/test/sass-graph/fixtures/load-path-cwd/outside-load-path/_b.scss new file mode 100644 index 000000000..cc7403e9d --- /dev/null +++ b/test/sass-graph/fixtures/load-path-cwd/outside-load-path/_b.scss @@ -0,0 +1,2 @@ +// Should not be loaded +@import 'c'; diff --git a/test/sass-graph/fixtures/load-path-cwd/outside-load-path/_c.scss b/test/sass-graph/fixtures/load-path-cwd/outside-load-path/_c.scss new file mode 100644 index 000000000..dd57526fc --- /dev/null +++ b/test/sass-graph/fixtures/load-path-cwd/outside-load-path/_c.scss @@ -0,0 +1,2 @@ +// Should not be loaded +@import 'd'; diff --git a/test/sass-graph/fixtures/load-path/_d.scss b/test/sass-graph/fixtures/load-path/_d.scss new file mode 100644 index 000000000..e69de29bb diff --git a/test/sass-graph/fixtures/load-path/index.scss b/test/sass-graph/fixtures/load-path/index.scss new file mode 100644 index 000000000..7b7f19389 --- /dev/null +++ b/test/sass-graph/fixtures/load-path/index.scss @@ -0,0 +1 @@ +@import "b"; diff --git a/test/sass-graph/fixtures/load-path/inside-load-path/_b.scss b/test/sass-graph/fixtures/load-path/inside-load-path/_b.scss new file mode 100644 index 000000000..1730d60a6 --- /dev/null +++ b/test/sass-graph/fixtures/load-path/inside-load-path/_b.scss @@ -0,0 +1 @@ +@import 'c'; diff --git a/test/sass-graph/fixtures/load-path/inside-load-path/_c.scss b/test/sass-graph/fixtures/load-path/inside-load-path/_c.scss new file mode 100644 index 000000000..f3d2df9f7 --- /dev/null +++ b/test/sass-graph/fixtures/load-path/inside-load-path/_c.scss @@ -0,0 +1 @@ +@import 'd'; diff --git a/test/sass-graph/fixtures/load-path/outside-load-path/_b.scss b/test/sass-graph/fixtures/load-path/outside-load-path/_b.scss new file mode 100644 index 000000000..cc7403e9d --- /dev/null +++ b/test/sass-graph/fixtures/load-path/outside-load-path/_b.scss @@ -0,0 +1,2 @@ +// Should not be loaded +@import 'c'; diff --git a/test/sass-graph/fixtures/load-path/outside-load-path/_c.scss b/test/sass-graph/fixtures/load-path/outside-load-path/_c.scss new file mode 100644 index 000000000..dd57526fc --- /dev/null +++ b/test/sass-graph/fixtures/load-path/outside-load-path/_c.scss @@ -0,0 +1,2 @@ +// Should not be loaded +@import 'd'; diff --git a/test/sass-graph/fixtures/mutliple-ancestors/_leaf.scss b/test/sass-graph/fixtures/mutliple-ancestors/_leaf.scss new file mode 100644 index 000000000..e69de29bb diff --git a/test/sass-graph/fixtures/mutliple-ancestors/entry_a.scss b/test/sass-graph/fixtures/mutliple-ancestors/entry_a.scss new file mode 100644 index 000000000..22356c28b --- /dev/null +++ b/test/sass-graph/fixtures/mutliple-ancestors/entry_a.scss @@ -0,0 +1 @@ +@import 'leaf.scss'; diff --git a/test/sass-graph/fixtures/mutliple-ancestors/entry_b.scss b/test/sass-graph/fixtures/mutliple-ancestors/entry_b.scss new file mode 100644 index 000000000..247536fa0 --- /dev/null +++ b/test/sass-graph/fixtures/mutliple-ancestors/entry_b.scss @@ -0,0 +1 @@ +@import 'leaf'; diff --git a/test/sass-graph/fixtures/mutliple-ancestors/entry_c.scss b/test/sass-graph/fixtures/mutliple-ancestors/entry_c.scss new file mode 100644 index 000000000..eacf63c73 --- /dev/null +++ b/test/sass-graph/fixtures/mutliple-ancestors/entry_c.scss @@ -0,0 +1 @@ +@import '_leaf'; diff --git a/test/sass-graph/fixtures/mutliple-ancestors/entry_d.scss b/test/sass-graph/fixtures/mutliple-ancestors/entry_d.scss new file mode 100644 index 000000000..acd3ad193 --- /dev/null +++ b/test/sass-graph/fixtures/mutliple-ancestors/entry_d.scss @@ -0,0 +1 @@ +@import '_leaf.scss'; diff --git a/test/sass-graph/fixtures/no-imports/index.scss b/test/sass-graph/fixtures/no-imports/index.scss new file mode 100644 index 000000000..3ce38db3b --- /dev/null +++ b/test/sass-graph/fixtures/no-imports/index.scss @@ -0,0 +1,4 @@ +@import 'no-such-file'; +@import 'no-such-file.css'; +@import 'no-such-file.scss'; +@import 'http://no-such-file.com'; diff --git a/test/sass-graph/fixtures/simple/_c.scss b/test/sass-graph/fixtures/simple/_c.scss new file mode 100644 index 000000000..632e3fcd1 --- /dev/null +++ b/test/sass-graph/fixtures/simple/_c.scss @@ -0,0 +1 @@ +@import 'nested/d'; diff --git a/test/sass-graph/fixtures/simple/b.scss b/test/sass-graph/fixtures/simple/b.scss new file mode 100644 index 000000000..08fd4c60c --- /dev/null +++ b/test/sass-graph/fixtures/simple/b.scss @@ -0,0 +1 @@ +@import 'c.scss'; diff --git a/test/sass-graph/fixtures/simple/index.scss b/test/sass-graph/fixtures/simple/index.scss new file mode 100644 index 000000000..7b7f19389 --- /dev/null +++ b/test/sass-graph/fixtures/simple/index.scss @@ -0,0 +1 @@ +@import "b"; diff --git a/test/sass-graph/fixtures/simple/nested/_d.scss b/test/sass-graph/fixtures/simple/nested/_d.scss new file mode 100644 index 000000000..4dc48aeeb --- /dev/null +++ b/test/sass-graph/fixtures/simple/nested/_d.scss @@ -0,0 +1 @@ +@import 'e'; diff --git a/test/sass-graph/fixtures/simple/nested/_e.scss b/test/sass-graph/fixtures/simple/nested/_e.scss new file mode 100644 index 000000000..e69de29bb diff --git a/test/sass-graph/parse-directory.js b/test/sass-graph/parse-directory.js new file mode 100644 index 000000000..f1421ed96 --- /dev/null +++ b/test/sass-graph/parse-directory.js @@ -0,0 +1,30 @@ +var assert = require('assert').strict; +var path = require('path'); +var sassGraph = require('../../lib/sass-graph/sass-graph'); +var graph = require('./util').graph; + +describe('sass-graph', function(){ + describe('parseDir', function () { + describe('with a simple graph', function() { + it('should return a graph', function() { + graph().fromFixtureDir('simple').assertDecendents([ + 'b.scss', + '_c.scss', + path.join('nested', '_d.scss'), + path.join('nested', '_e.scss'), + ]); + }); + }); + + describe('with mutliple ancestors', function() { + it('should return a graph', function() { + graph().fromFixtureDir('mutliple-ancestors').assertAncestors('_leaf.scss', [ + 'entry_a.scss', + 'entry_b.scss', + 'entry_c.scss', + 'entry_d.scss', + ]); + }); + }); + }); +}); diff --git a/test/sass-graph/parse-file.js b/test/sass-graph/parse-file.js new file mode 100644 index 000000000..213cbfe62 --- /dev/null +++ b/test/sass-graph/parse-file.js @@ -0,0 +1,164 @@ + +var assert = require('assert').strict; +var path = require('path'); +var sassGraph = require('../../lib/sass-graph/sass-graph'); +var fixture = require('./util').fixture; +var graph = require('./util').graph; + +describe('sass-graph', function(){ + describe('parseFile', function () { + describe('with a simple graph', function() { + it('should return a graph', function() { + graph().fromFixtureFile('simple') + .assertDecendents([ + 'b.scss', + '_c.scss', + path.join('nested', '_d.scss'), + path.join('nested', '_e.scss'), + ]) + .assertAncestors(path.join('nested', '_e.scss'), [ + path.join('nested', '_d.scss'), + '_c.scss', + 'b.scss', + 'index.scss', + ]) + .assertAncestors(path.join('nested', '_d.scss'), [ + '_c.scss', + 'b.scss', + 'index.scss', + ]) + .assertAncestors('_c.scss', [ + 'b.scss', + 'index.scss', + ]) + .assertAncestors('b.scss', [ + 'index.scss', + ]) + .assertAncestors('index.scss', []); + }); + }); + + describe('with a simple graph in indented syntax', function() { + it('should return a graph', function() { + graph().indented().fromFixtureFile('indented-syntax') + .assertDecendents([ + 'b.sass', + '_c.sass', + path.join('nested', '_d.sass'), + path.join('nested', '_e.sass'), + ]) + .assertAncestors(path.join('nested', '_e.sass'), [ + path.join('nested', '_d.sass'), + '_c.sass', + 'b.sass', + 'index.sass', + ]) + .assertAncestors(path.join('nested', '_d.sass'), [ + '_c.sass', + 'b.sass', + 'index.sass', + ]) + .assertAncestors('_c.sass', [ + 'b.sass', + 'index.sass', + ]) + .assertAncestors('b.sass', [ + 'index.sass', + ]) + .assertAncestors('index.sass', []); + }); + }); + + describe('with a graph with loadPaths', function() { + it('should return a graph', function() { + var includeFolder = 'inside-load-path'; + var excludeFolder = 'outside-load-path'; + var rootFolder = path.dirname(fixture('load-path')()); + var opts = { loadPaths: [ rootFolder, fixture('load-path')(includeFolder) ] }; + + graph(opts).fromFixtureFile('load-path') + .assertDecendents([ + path.join(includeFolder, '_b.scss'), + path.join(includeFolder, '_c.scss'), + '_d.scss', + ]) + .assertAncestors(path.join('_d.scss'), [ + path.join(includeFolder, '_c.scss'), + path.join(includeFolder, '_b.scss'), + 'index.scss', + ]) + .assertAncestors(path.join(includeFolder, '_c.scss'), [ + path.join(includeFolder, '_b.scss'), + 'index.scss', + ]) + .assertAncestors(path.join(includeFolder, '_b.scss'), [ + 'index.scss', + ]) + .assertAncestors('index.scss', []) + .assertAncestors(path.join(excludeFolder, '_b.scss'), []) + .assertAncestors(path.join(excludeFolder, '_c.scss'), []); + }); + + it('should prioritize cwd', function() { + var includeFolder = 'inside-load-path'; + var excludeFolder = 'outside-load-path'; + var opts = { loadPaths: [ fixture('load-path-cwd')(includeFolder) ] }; + + graph(opts).fromFixtureFile('load-path-cwd').assertDecendents([ + '_b.scss', + path.join(includeFolder, '_c.scss'), + ]) + .assertAncestors(path.join(includeFolder, '_c.scss'), [ + '_b.scss', + 'index.scss', + ]) + .assertAncestors('_b.scss', [ + 'index.scss', + ]) + .assertAncestors('index.scss', []) + .assertAncestors(path.join(includeFolder, '_b.scss'), []) + .assertAncestors(path.join(excludeFolder, '_b.scss'), []) + .assertAncestors(path.join(excludeFolder, '_c.scss'), []); + }); + }); + + describe('with a folder that has an extension', function() { + it('should return a graph', function () { + var leaf = path.join('_nested.scss', '_leaf.scss'); + graph().fromFixtureFile('folder-with-extension') + .assertDecendents([leaf]) + .assertAncestors(leaf, ['index.scss']); + }); + }); + + describe('with no imports', function() { + it('should return a graph', function () { + graph().fromFixtureFile('no-imports').assertDecendents([]); + }); + }); + + describe('when the file is inaccessible', function() { + it('should thow an error', function () { + assert.throws(function() { + graph().fromFixtureFile('no-such-path'); + }); + }); + }); + + describe('with options', function() { + it('should not use inherited propterties', function() { + before(function() { + Array.prototype.foo = function() {}; + }); + + after(function() { + delete Array.prototype.foo; + }); + + assert.doesNotThrow(function() { + graph().fromFixtureFile('no-imports').assertDecendents([]); + }); + }); + }); + }); +}); diff --git a/test/sass-graph/parse-imports.js b/test/sass-graph/parse-imports.js new file mode 100644 index 000000000..9f173bacc --- /dev/null +++ b/test/sass-graph/parse-imports.js @@ -0,0 +1,155 @@ +var parseImports = require('../../lib/sass-graph/parse-imports'); +var assert = require('assert').strict; +var path = require('path'); + +describe('parse-imports', function () { + it('should parse single import with single quotes', function () { + var scss = "@import 'app';"; + var result = parseImports(scss); + assert.deepEqual(['app'], result); + }); + + it('should parse single import with double quotes', function () { + var scss = '@import "app";'; + var result = parseImports(scss); + assert.deepEqual(['app'], result); + }); + + it('should parse single import without quotes', function () { + var scss = '@import app;'; + var result = parseImports(scss); + assert.deepEqual(['app'], result); + }); + + it('should parse single import without quotes in indented syntax', function () { + var scss = '@import app'; + var result = parseImports(scss, true); + assert.deepEqual(['app'], result); + }); + + it('should parse unquoted import', function () { + var scss = '@import include/app;\n' + + '@import include/foo,\n' + + 'include/bar;'; + var result = parseImports(scss); + assert.deepEqual(['include/app', 'include/foo', 'include/bar'], result); + }); + + it('should parse single import with extra spaces after import', function () { + var scss = '@import "app";'; + var result = parseImports(scss); + assert.deepEqual(['app'], result); + }); + + it('should parse single import with extra spaces before ;', function () { + var scss = '@import "app" ;'; + var result = parseImports(scss); + assert.deepEqual(['app'], result); + }); + + it('should parse two individual imports', function () { + var scss = '@import "app"; \n' + + '@import "foo"; \n'; + var result = parseImports(scss); + assert.deepEqual(['app', 'foo'], result); + }); + + it('should parse two imports on same line', function () { + var scss = '@import "app", "foo";'; + var result = parseImports(scss); + assert.deepEqual(['app', 'foo'], result); + }); + + it('should parse two imports continued on multiple lines ', function () { + var scss = '@import "app", \n' + + '"foo"; \n'; + var result = parseImports(scss); + assert.deepEqual(['app', 'foo'], result); + }); + + it('should parse three imports with mixed style ', function () { + var scss = '@import "app", \n' + + '"foo"; \n' + + '@import "bar";'; + var result = parseImports(scss); + assert.deepEqual(['app', 'foo', 'bar'], result); + }); + + it('should not parse import in CSS comment', function () { + var scss = '@import "app"; \n' + + '/*@import "foo";*/ \n' + + '/*@import "nav"; */ \n' + + '@import /*"bar"; */ "baz"; \n' + + '@import /*"bar";*/ "bam"'; + var result = parseImports(scss); + assert.deepEqual(['app', 'baz', 'bam'], result); + }); + + it('should not parse import in Sass comment', function () { + var scss = '@import "app"; \n' + + '//@import "foo"; \n' + + '@import //"bar"; \n'+ + '"baz"'; + var result = parseImports(scss); + assert.deepEqual(['app', 'baz'], result); + }); + + it('should not parse import in any comment', function () { + var scss = '@import \n' + + '// app imports foo\n' + + '"app",\n' + + '\n' + + '/** do not import bar;\n' + + ' "bar"\n' + + '*/\n' + + '\n' + + '// do not import nav: "d",\n' + + '\n' + + '// footer imports nothing else\n' + + '"baz"'; + var result = parseImports(scss); + assert.deepEqual(['app', 'baz'], result); + }); + + it('should throw error when invalid @import syntax is encountered', function () { + var scss = '@import "a"\n' + + '@import "b";'; + assert.throws(function() { + parseImports(scss); + }); + }); + + it('should not throw error when invalid @import syntax is encountered using indented syntax', function () { + var scss = '@import a\n' + + '@import b'; + assert.doesNotThrow(function() { + parseImports(scss, true); + }); + }); + + it('should parse a full css file', function () { + var scss = '@import url("a.css");\n' + + '@import url("b.scss");\n' + + '@import "c.scss";\n' + + '@import "d";\n' + + '@import "app1", "foo1";\n' + + '@import "app2",\n' + + ' "foo2";\n' + + '/********\n' + + 'reset\n' + + '*********/\n' + + '/*\n' + + 'table{\n' + + ' border-collapse: collapse;\n' + + ' width: 100%;\n' + + '}\n' + + '\n' + + ' [class*="jimu"],\n' + + ' [class*="jimu"] * {\n' + + ' -moz-box-sizing: border-box;\n' + + ' box-sizing: border-box;\n' + + '}'; + var result = parseImports(scss); + assert.deepEqual(["c.scss", "d", "app1", "foo1", "app2", "foo2"], result); + }); +}); diff --git a/test/sass-graph/util.js b/test/sass-graph/util.js new file mode 100644 index 000000000..9398fb218 --- /dev/null +++ b/test/sass-graph/util.js @@ -0,0 +1,68 @@ +var assert = require('assert').strict; +var fs = require('fs'); +var path = require('path'); +var sassGraph = require('../../lib/sass-graph/sass-graph'); + +var fixtures = path.resolve(path.join('test', 'sass-graph', 'fixtures')); + +var fixture = function(name) { + return function(file) { + if (!file) file = 'index.scss'; + return path.join(fixtures, name, file); + }; +}; + +var graph = function(opts) { + var instance, dir, isIndentedSyntax; + + function indexFile() { + if (isIndentedSyntax) { + return 'index.sass'; + } + return 'index.scss'; + }; + + return { + indented: function() { + isIndentedSyntax = true; + return this; + }, + + fromFixtureDir: function(name) { + dir = fixture(name); + instance = sassGraph.parseDir(path.dirname(dir(indexFile())), opts); + return this; + }, + + fromFixtureFile: function(name) { + dir = fixture(name); + instance = sassGraph.parseFile(dir(indexFile()), opts); + return this; + }, + + assertDecendents: function(expected) { + var actual = []; + + instance.visitDescendents(dir(indexFile()), function(imp) { + actual.push(imp) + }); + + assert.deepEqual(expected.map(dir), actual); + return this; + }, + + assertAncestors: function(file, expected) { + var actual = []; + + instance.visitAncestors(dir(file), function(imp) { + actual.push(imp) + }); + + assert.deepEqual(expected.map(dir), actual); + return this; + }, + }; +}; + +module.exports.fixture = fixture; +module.exports.graph = graph; From 950920d5291cb0f34188bf66a0218decedf9c110 Mon Sep 17 00:00:00 2001 From: Koza Date: Wed, 10 Nov 2021 20:31:48 +0100 Subject: [PATCH 2/2] Fix LGTM alert --- lib/sass-graph/sass-graph.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sass-graph/sass-graph.js b/lib/sass-graph/sass-graph.js index 5d5aba0ef..7f329b840 100644 --- a/lib/sass-graph/sass-graph.js +++ b/lib/sass-graph/sass-graph.js @@ -9,7 +9,7 @@ var parseImports = require('./parse-imports'); // resolve a sass module to a path function resolveSassPath(sassPath, loadPaths, extensions) { // trim sass file extensions - var re = new RegExp('(\.('+extensions.join('|')+'))$', 'i'); + var re = new RegExp('(.('+extensions.join('|')+'))$', 'i'); var sassPathName = sassPath.replace(re, ''); // check all load paths var i, j, length = loadPaths.length, scssPath, partialPath;