/ src / syntax-scope-map.js
syntax-scope-map.js
  1  const parser = require('postcss-selector-parser');
  2  
  3  module.exports = class SyntaxScopeMap {
  4    constructor(resultsBySelector) {
  5      this.namedScopeTable = {};
  6      this.anonymousScopeTable = {};
  7      for (let selector in resultsBySelector) {
  8        this.addSelector(selector, resultsBySelector[selector]);
  9      }
 10      setTableDefaults(this.namedScopeTable, true);
 11      setTableDefaults(this.anonymousScopeTable, false);
 12    }
 13  
 14    addSelector(selector, result) {
 15      parser(parseResult => {
 16        for (let selectorNode of parseResult.nodes) {
 17          let currentTable = null;
 18          let currentIndexValue = null;
 19  
 20          for (let i = selectorNode.nodes.length - 1; i >= 0; i--) {
 21            const termNode = selectorNode.nodes[i];
 22  
 23            switch (termNode.type) {
 24              case 'tag':
 25                if (!currentTable) currentTable = this.namedScopeTable;
 26                if (!currentTable[termNode.value])
 27                  currentTable[termNode.value] = {};
 28                currentTable = currentTable[termNode.value];
 29                if (currentIndexValue != null) {
 30                  if (!currentTable.indices) currentTable.indices = {};
 31                  if (!currentTable.indices[currentIndexValue])
 32                    currentTable.indices[currentIndexValue] = {};
 33                  currentTable = currentTable.indices[currentIndexValue];
 34                  currentIndexValue = null;
 35                }
 36                break;
 37  
 38              case 'string':
 39                if (!currentTable) currentTable = this.anonymousScopeTable;
 40                const value = termNode.value.slice(1, -1).replace(/\\"/g, '"');
 41                if (!currentTable[value]) currentTable[value] = {};
 42                currentTable = currentTable[value];
 43                if (currentIndexValue != null) {
 44                  if (!currentTable.indices) currentTable.indices = {};
 45                  if (!currentTable.indices[currentIndexValue])
 46                    currentTable.indices[currentIndexValue] = {};
 47                  currentTable = currentTable.indices[currentIndexValue];
 48                  currentIndexValue = null;
 49                }
 50                break;
 51  
 52              case 'universal':
 53                if (currentTable) {
 54                  if (!currentTable['*']) currentTable['*'] = {};
 55                  currentTable = currentTable['*'];
 56                } else {
 57                  if (!this.namedScopeTable['*']) {
 58                    this.namedScopeTable['*'] = this.anonymousScopeTable[
 59                      '*'
 60                    ] = {};
 61                  }
 62                  currentTable = this.namedScopeTable['*'];
 63                }
 64                if (currentIndexValue != null) {
 65                  if (!currentTable.indices) currentTable.indices = {};
 66                  if (!currentTable.indices[currentIndexValue])
 67                    currentTable.indices[currentIndexValue] = {};
 68                  currentTable = currentTable.indices[currentIndexValue];
 69                  currentIndexValue = null;
 70                }
 71                break;
 72  
 73              case 'combinator':
 74                if (currentIndexValue != null) {
 75                  rejectSelector(selector);
 76                }
 77  
 78                if (termNode.value === '>') {
 79                  if (!currentTable.parents) currentTable.parents = {};
 80                  currentTable = currentTable.parents;
 81                } else {
 82                  rejectSelector(selector);
 83                }
 84                break;
 85  
 86              case 'pseudo':
 87                if (termNode.value === ':nth-child') {
 88                  currentIndexValue = termNode.nodes[0].nodes[0].value;
 89                } else {
 90                  rejectSelector(selector);
 91                }
 92                break;
 93  
 94              default:
 95                rejectSelector(selector);
 96            }
 97          }
 98  
 99          currentTable.result = result;
100        }
101      }).process(selector);
102    }
103  
104    get(nodeTypes, childIndices, leafIsNamed = true) {
105      let result;
106      let i = nodeTypes.length - 1;
107      let currentTable = leafIsNamed
108        ? this.namedScopeTable[nodeTypes[i]]
109        : this.anonymousScopeTable[nodeTypes[i]];
110  
111      if (!currentTable) currentTable = this.namedScopeTable['*'];
112  
113      while (currentTable) {
114        if (currentTable.indices && currentTable.indices[childIndices[i]]) {
115          currentTable = currentTable.indices[childIndices[i]];
116        }
117  
118        if (currentTable.result != null) {
119          result = currentTable.result;
120        }
121  
122        if (i === 0) break;
123        i--;
124        currentTable =
125          currentTable.parents &&
126          (currentTable.parents[nodeTypes[i]] || currentTable.parents['*']);
127      }
128  
129      return result;
130    }
131  };
132  
133  function setTableDefaults(table, allowWildcardSelector) {
134    const defaultTypeTable = allowWildcardSelector ? table['*'] : null;
135  
136    for (let type in table) {
137      let typeTable = table[type];
138      if (typeTable === defaultTypeTable) continue;
139  
140      if (defaultTypeTable) {
141        mergeTable(typeTable, defaultTypeTable);
142      }
143  
144      if (typeTable.parents) {
145        setTableDefaults(typeTable.parents, true);
146      }
147  
148      for (let key in typeTable.indices) {
149        const indexTable = typeTable.indices[key];
150        mergeTable(indexTable, typeTable, false);
151        if (indexTable.parents) {
152          setTableDefaults(indexTable.parents, true);
153        }
154      }
155    }
156  }
157  
158  function mergeTable(table, defaultTable, mergeIndices = true) {
159    if (mergeIndices && defaultTable.indices) {
160      if (!table.indices) table.indices = {};
161      for (let key in defaultTable.indices) {
162        if (!table.indices[key]) table.indices[key] = {};
163        mergeTable(table.indices[key], defaultTable.indices[key]);
164      }
165    }
166  
167    if (defaultTable.parents) {
168      if (!table.parents) table.parents = {};
169      for (let key in defaultTable.parents) {
170        if (!table.parents[key]) table.parents[key] = {};
171        mergeTable(table.parents[key], defaultTable.parents[key]);
172      }
173    }
174  
175    if (defaultTable.result != null && table.result == null) {
176      table.result = defaultTable.result;
177    }
178  }
179  
180  function rejectSelector(selector) {
181    throw new TypeError(`Unsupported selector '${selector}'`);
182  }