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 }