1'use strict' 2 3var css = require('css') 4var util = require('./lib/util') 5var validateItem = require('./lib/validator').validate 6var fs = require('fs') 7var path = require('path') 8var lodash = require('lodash') 9 10var SELECTOR_MATCHER = /^[\.#]?[A-Za-z0-9_\-:]+$/ 11var DESCENDANT_SELECTOR_MATCHER = /^([.#]?[A-Za-z0-9_-]+(\s+|\s*>\s*))+([.#]?[A-Za-z0-9_\-:]+)$/ 12var IMPORT_MATCHER = /(['"]([^()]+?)['"])|(['"]([^()]+?)['"]\s+(only|not)?\s?(screen)?\s?((and|or|,|not|landscape)?\s?[(]([^()])+[)]\s*)+)/g 13var LENGTH_REGEXP = /^[-+]?\d*\.?\d+(\S*)$/ 14const CARD_SELECTOR = /^[\.#][A-Za-z0-9_\-]+$/ 15const card = process.env.DEVICE_LEVEL === 'card' 16var ALL_SELECTOR_MATCHER = /^\*$/ 17var ATTRIBUTE_SELECTOR = /^\[+(?![0-9])\w{0,}(\s*=\s*)((?![0-9])\w{0,}|\"\w{0,}\")\]+$/ 18var ELEMENT_AND_ELEMENT = /^[a-zA-Z][a-zA-Z-]{0,}\s{0,}(\+\s{0,}[a-zA-Z][a-zA-Z-]{0,})+$/ 19var CONTENT_ID = /^[a-zA-Z][a-zA-Z-]{0,}([#][a-zA-Z][a-zA-Z0-9-]{0,})(.+?::(after|before))+$/ 20 21/** 22 * expand margin、padding、border、borderWidth、borderColor、borderStyle properties、animation 23 * 24 * @param {object} subResult 25 * @param {String} camelCasedName 26 * @param {object} ruleResult 27 */ 28function expand (subResult, camelCasedName, ruleResult) { 29 if (camelCasedName === 'border') { 30 subResult.value.forEach(function (item) { 31 if (item.type === 'Width' || item.type === 'Color' || item.type === 'Style') { 32 const spliceName = [camelCasedName + 'Top' + item.type, camelCasedName + 'Right' + item.type, camelCasedName + 33 'Bottom' + item.type, camelCasedName + 'Left' + item.type] 34 util.splitAttr(ruleResult, item.value, spliceName) 35 } 36 else { 37 ruleResult[camelCasedName + item.type] = item.value 38 } 39 }) 40 } 41 else if (['borderTop', 'borderRight', 'borderBottom', 'borderLeft'].includes(camelCasedName)) { 42 subResult.value.forEach(function (item) { 43 ruleResult[camelCasedName + item.type] = item.value 44 }) 45 } 46 else if (camelCasedName === 'margin' || camelCasedName === 'padding') { 47 const spliceName = [camelCasedName + 'Top', camelCasedName + 'Right', 48 camelCasedName + 'Bottom', camelCasedName + 'Left'] 49 util.splitAttr(ruleResult, subResult.value, spliceName) 50 } 51 else if (camelCasedName === 'borderWidth') { 52 util.splitAttr(ruleResult, subResult.value, ['borderTopWidth', 'borderRightWidth', 53 'borderBottomWidth', 'borderLeftWidth']) 54 } 55 else if (camelCasedName === 'borderColor') { 56 util.splitAttr(ruleResult, subResult.value, ['borderTopColor', 'borderRightColor', 57 'borderBottomColor', 'borderLeftColor']) 58 } 59 else if (camelCasedName === 'borderStyle') { 60 util.splitAttr(ruleResult, subResult.value, ['borderTopStyle', 'borderRightStyle', 61 'borderBottomStyle', 'borderLeftStyle']) 62 } 63 else if (camelCasedName === 'borderRadius') { 64 util.splitAttr(ruleResult, subResult.value, ['borderBottomLeftRadius', 'borderBottomRightRadius', 65 'borderTopLeftRadius', 'borderTopRightRadius']) 66 } 67 else if (camelCasedName === 'gridGap') { 68 util.splitAttr(ruleResult, subResult.value, ['gridRowsGap', 'gridColumnsGap']) 69 } 70 else if (camelCasedName === 'boxShadow') { 71 subResult.value.forEach(function (item) { 72 if (item.type === 'H' || item.type === 'V' || item.type === 'Blur' || item.type === 'Spread' || 73 item.type === 'Color') { 74 util.splitAttr(ruleResult, item.value, [camelCasedName + item.type]) 75 } 76 }) 77 } 78 else if (camelCasedName === 'animation') { 79 Object.assign(ruleResult, subResult.value); 80 } 81 else { 82 // never to do 83 } 84} 85 86/** 87 * expand flex style 88 * 89 * @param {object} rule 90 * @param {Array} ruleLog 91 */ 92function flexExpand(rule, ruleLog) { 93 for (let i = 0; i < rule.declarations.length; i++) { 94 let declaration = rule.declarations[i] 95 if (declaration.property === 'flex') { 96 let values = declaration.value.split(/\s+/) 97 rule.declarations.splice(i, 1) 98 if (values.length === 1) { 99 checkFlexOne(rule, ruleLog, declaration, values, i) 100 } else if (values.length === 2) { 101 checkFlexTwo(rule, ruleLog, declaration, values, i) 102 } else if (values.length === 3) { 103 checkFlexThree(rule, ruleLog, declaration, values, i) 104 } else { 105 ruleLog.push({ 106 line: declaration.position.start.line, 107 column: declaration.position.start.column, 108 reason: 'ERROR: Value `' + declaration.value + '` of the `' + 109 declaration.property + '` attribute is incorrect.' 110 }) 111 } 112 } 113 } 114} 115 116function getUnit(value) { 117 value = value.toString().trim() 118 let match = value.match(LENGTH_REGEXP) 119 if (match) { 120 let unit = match[1] 121 if (unit) { 122 if (unit === 'px') { 123 return "px" 124 } 125 } else { 126 return "none" 127 } 128 } 129 return null 130} 131 132function checkFlexOne(rule, ruleLog, declaration, values, i) { 133 const array = ['none', 'auto', 'initial'] 134 if (array.includes(values[0])) { 135 rule.declarations.splice(i, 0, {type: 'declaration', property: 'flex', 136 value: values[0], position: declaration.position}) 137 } else if (getUnit(values[0]) === 'px') { 138 rule.declarations.splice(i, 0, {type: 'declaration', property: 'flex-basis', 139 value: values[0], position: declaration.position}) 140 } else if (getUnit(values[0]) === 'none') { 141 rule.declarations.splice(i, 0, {type: 'declaration', property: 'flex-grow', 142 value: values[0], position: declaration.position}) 143 } else { 144 ruleLog.push({ 145 line: declaration.position.start.line, 146 column: declaration.position.start.column, 147 reason: 'ERROR: Value `' + declaration.value + '` of the `' + declaration.property + 148 '` attribute is incorrect.' + 'It must be a number, a number with unit `' + 'px`' + 149 ', none, auto, or initial.' 150 }) 151 } 152} 153 154function checkFlexTwo(rule, ruleLog, declaration, values, i) { 155 if (getUnit(values[0]) === 'none') { 156 rule.declarations.splice(i, 0, {type: 'declaration', property: 'flex-grow', 157 value: values[0], position: declaration.position}) 158 if (getUnit(values[1]) === 'px') { 159 rule.declarations.splice(i, 0, {type: 'declaration', property: 'flex-basis', 160 value: values[1], position: declaration.position}) 161 } else if (getUnit(values[1]) === 'none') { 162 rule.declarations.splice(i, 0, {type: 'declaration', property: 'flex-shrink', 163 value: values[1], position: declaration.position}) 164 } else { 165 ruleLog.push({ 166 line: declaration.position.start.line, 167 column: declaration.position.start.column, 168 reason: 'ERROR: Value `' + declaration.value + '` of the `' + declaration.property + 169 '` attribute is incorrect. Value `' + values[1] + 170 '` must be a number or a number with unit `' + 'px`.' 171 }) 172 } 173 } else { 174 ruleLog.push({ 175 line: declaration.position.start.line, 176 column: declaration.position.start.column, 177 reason: 'ERROR: Value `' + declaration.value + '` of the `' + 178 declaration.property + '` attribute is incorrect. Value `' + values[0] + '` must be a number.' 179 }) 180 } 181} 182 183function checkFlexThree(rule, ruleLog, declaration, values, i) { 184 if (getUnit(values[0]) === 'none' && getUnit(values[1]) === 'none' && getUnit(values[2]) === 'px') { 185 rule.declarations.splice(i, 0, {type: 'declaration', property: 'flex-grow', 186 value: values[0], position: declaration.position}) 187 rule.declarations.splice(i, 0, {type: 'declaration', property: 'flex-shrink', 188 value: values[1], position: declaration.position}) 189 rule.declarations.splice(i, 0, {type: 'declaration', property: 'flex-basis', 190 value: values[2], position: declaration.position}) 191 } else { 192 ruleLog.push({ 193 line: declaration.position.start.line, 194 column: declaration.position.start.column, 195 reason: 'ERROR: Value `' + declaration.value + '` of the `' + declaration.property + 196 '` attribute is incorrect. It must be in the format of (1, 1, 1px).' 197 }) 198 } 199} 200 201/** 202 * Parse `<style>` code to a JSON Object and log errors & warnings 203 * 204 * @param {string} code 205 * @param {function} done which will be called with 206 * - err:Error 207 * - data.jsonStyle{}: `classname.propname.value`-like object 208 * - data.log[{line, column, reason}] 209 */ 210function parse(code, done, resourcePath) { 211 var ast, err, jsonStyle = {}, log = [] 212 213 // css parse 214 ast = css.parse(code, {silent: true, source: resourcePath}); 215 216 // catch syntax error 217 if (ast.stylesheet.parsingErrors && ast.stylesheet.parsingErrors.length) { 218 err = ast.stylesheet.parsingErrors 219 err.forEach(function (error) { 220 log.push({line: error.line, column: error.column, reason: error.toString().replace('Error', 'ERROR')}) 221 }) 222 } 223 224 // walk all 225 /* istanbul ignore else */ 226 if (ast && ast.type === 'stylesheet' && ast.stylesheet && 227 ast.stylesheet.rules && ast.stylesheet.rules.length) { 228 ast.stylesheet.rules.forEach(function (rule) { 229 var type = rule.type 230 var ruleResult = {} 231 var ruleLog = [] 232 233 if (type === 'rule') { 234 if (rule.declarations && rule.declarations.length) { 235 flexExpand(rule, ruleLog) 236 237 rule.declarations.forEach(function (declaration) { 238 var subType = declaration.type 239 var name, value, line, column, subResult, camelCasedName 240 241 /* istanbul ignore if */ 242 if (subType !== 'declaration') { 243 return 244 } 245 246 name = declaration.property 247 value = declaration.value 248 249 // validate declarations and collect them to result 250 camelCasedName = util.hyphenedToCamelCase(name) 251 subResult = validateItem(camelCasedName, value) 252 253 // expand margin、padding、border、borderWidth、borderColor、borderStyle properties、animation 254 if (subResult.value && Object.values(util.SPLECIAL_ATTR).indexOf(camelCasedName) !== -1) { 255 expand(subResult, camelCasedName, ruleResult) 256 } 257 258 /* istanbul ignore else */ 259 if ((typeof subResult.value === 'number' || typeof subResult.value === 'string') 260 && !Object.values(util.SPLECIAL_ATTR).includes(camelCasedName)) { 261 ruleResult[camelCasedName] = subResult.value 262 } 263 if (subResult.log) { 264 subResult.log.line = declaration.position.start.line 265 subResult.log.column = declaration.position.start.column 266 ruleLog.push(subResult.log) 267 } 268 }) 269 270 if (card && rule.selectors.length > 1) { 271 log.push({ 272 line: rule.position.start.line, 273 column: rule.position.start.column, 274 reason: 'ERROR: The `' + rule.selectors.join(', ') + '` selector is not supported.' 275 }) 276 } else { 277 rule.selectors.forEach(function (selector) { 278 const flag = card ? selector.match(CARD_SELECTOR) : 279 selector.match(SELECTOR_MATCHER) || selector.match(DESCENDANT_SELECTOR_MATCHER) || 280 selector.match(ALL_SELECTOR_MATCHER) || selector.match(ATTRIBUTE_SELECTOR) || 281 selector.match(ELEMENT_AND_ELEMENT) || selector.match(CONTENT_ID) 282 if (flag) { 283 var className = selector 284 285 // handle pseudo class 286 var pseudoIndex = className.indexOf(':') 287 if (pseudoIndex > -1) { 288 var pseudoCls = className.slice(pseudoIndex) 289 className = className.slice(0, pseudoIndex) 290 var pseudoRuleResult = {} 291 Object.keys(ruleResult).forEach(function (prop) { 292 pseudoRuleResult[prop + pseudoCls] = ruleResult[prop] 293 }) 294 ruleResult = pseudoRuleResult 295 } 296 297 // merge style 298 Object.keys(ruleResult).forEach(function (prop) { 299 // handle transition 300 if (prop.indexOf('transition') === 0 && prop !== 'transition') { 301 var realProp = prop.replace('transition', '') 302 realProp = realProp[0].toLowerCase() + realProp.slice(1) 303 jsonStyle['@TRANSITION'] = jsonStyle['@TRANSITION'] || {} 304 jsonStyle['@TRANSITION'][className] = jsonStyle['@TRANSITION'][className] || {} 305 jsonStyle['@TRANSITION'][className][realProp] = ruleResult[prop] 306 } 307 308 jsonStyle[className] = jsonStyle[className] || {} 309 jsonStyle[className][prop] = ruleResult[prop] 310 }) 311 } else { 312 log.push({ 313 line: rule.position.start.line, 314 column: rule.position.start.column, 315 reason: 'ERROR: The `' + selector + '` selector is not supported.' 316 }) 317 } 318 }) 319 } 320 log = log.concat(ruleLog) 321 } 322 } 323 /* istanbul ignore else */ 324 else if (type === 'font-face') { 325 /* istanbul ignore else */ 326 if (rule.declarations && rule.declarations.length) { 327 rule.declarations.forEach(function (declaration) { 328 /* istanbul ignore if */ 329 if (declaration.type !== 'declaration') { 330 return 331 } 332 var name = util.hyphenedToCamelCase(declaration.property) 333 var value = declaration.value 334 if (name === 'fontFamily' && '\"\''.indexOf(value[0]) > -1) { 335 value = value.slice(1, value.length - 1) 336 } 337 ruleResult[name] = value 338 }) 339 if (!jsonStyle['@FONT-FACE']) { 340 jsonStyle['@FONT-FACE'] = [] 341 } 342 jsonStyle['@FONT-FACE'].push(ruleResult) 343 } 344 } 345 else if (type === 'import') { 346 parseImport(resourcePath, rule, jsonStyle, log) 347 } 348 else if (type === 'keyframes' && !card) { 349 if (!jsonStyle['@KEYFRAMES']) { 350 jsonStyle['@KEYFRAMES'] = {} 351 } 352 var keyName = rule.name 353 jsonStyle['@KEYFRAMES'][keyName] = [] 354 if (rule.keyframes && rule.keyframes.length) { 355 if (card) { 356 log.push({ 357 line: rule.position.start.line, 358 column: rule.position.start.column, 359 reason: 'ERROR: The keyframes is not supported!' 360 }) 361 } else { 362 rule.keyframes.forEach(function (keyframe) { 363 364 var keyframeType = keyframe.type 365 366 /* istanbul ignore if */ 367 if (keyframeType !== 'keyframe') { 368 return 369 } 370 371 if (keyframe.declarations && keyframe.declarations.length) { 372 keyframe.declarations.forEach(function (declaration) { 373 var subType = declaration.type 374 var name, value, line, column, subResult, camelCasedName 375 376 /* istanbul ignore if */ 377 if (subType !== 'declaration') { 378 return 379 } 380 381 name = declaration.property 382 value = declaration.value 383 384 // validate declarations and collect them to result 385 camelCasedName = util.hyphenedToCamelCase(name) 386 subResult = validateItem(camelCasedName, value) 387 388 // expand margin、padding、border、borderWidth、borderColor、borderStyle properties 389 if (subResult.value && Object.values(util.SPLECIAL_ATTR).indexOf(camelCasedName) !== -1) { 390 expand(subResult, camelCasedName, ruleResult) 391 } 392 /* istanbul ignore else */ 393 if ((typeof subResult.value === 'number' || typeof subResult.value === 'string') 394 && !Object.values(util.SPLECIAL_ATTR).includes(camelCasedName)) { 395 ruleResult[camelCasedName] = subResult.value 396 } 397 if (subResult.log) { 398 subResult.log.line = declaration.position.start.line 399 subResult.log.column = declaration.position.start.column 400 ruleLog.push(subResult.log) 401 } 402 }) 403 } 404 405 if (keyframe.values[0] === 'from') { 406 var keyframeResult = {} 407 Object.keys(ruleResult).forEach(function (prop) { 408 keyframeResult[prop] = ruleResult[prop] 409 }) 410 keyframeResult['time'] = 0 411 jsonStyle['@KEYFRAMES'][keyName].push(keyframeResult) 412 } 413 if (keyframe.values[0] === 'to') { 414 var keyframeResult = {} 415 Object.keys(ruleResult).forEach(function (prop) { 416 keyframeResult[prop] = ruleResult[prop] 417 }) 418 keyframeResult['time'] = 100 419 jsonStyle['@KEYFRAMES'][keyName].push(keyframeResult) 420 } 421 var patt = new RegExp(/^(100|[1-9]?\d)%$/) 422 if (patt.test(keyframe.values[0])) { 423 var keyframeResult = {} 424 Object.keys(ruleResult).forEach(function (prop) { 425 keyframeResult[prop] = ruleResult[prop] 426 }) 427 keyframeResult['time'] = keyframe.values[0].replace("%", "") 428 jsonStyle['@KEYFRAMES'][keyName].push(keyframeResult) 429 } 430 }) 431 log = log.concat(ruleLog) 432 } 433 } 434 } 435 else if (type === 'media') { 436 if (!jsonStyle['@MEDIA']) { 437 jsonStyle['@MEDIA'] = [] 438 } 439 var condition = rule.media 440 var mediaObj = {} 441 mediaObj['condition'] = condition 442 443 if (rule.rules && rule.rules.length) { 444 rule.rules.forEach(function(rule) { 445 ruleResult = {} 446 if (rule.type === 'import') { 447 parseImport(resourcePath, rule, mediaObj, log) 448 } 449 if (rule.declarations && rule.declarations.length) { 450 flexExpand(rule, ruleLog) 451 rule.declarations.forEach(function (declaration) { 452 var subType = declaration.type 453 var name, value, line, column, subResult, camelCasedName 454 455 /* istanbul ignore if */ 456 if (subType !== 'declaration') { 457 return 458 } 459 460 name = declaration.property 461 value = declaration.value 462 463 // validate declarations and collect them to result 464 camelCasedName = util.hyphenedToCamelCase(name) 465 subResult = validateItem(camelCasedName, value) 466 // expand margin、padding、border、borderWidth、borderColor、borderStyle properties 467 if (subResult.value && Object.values(util.SPLECIAL_ATTR).indexOf(camelCasedName) !== -1) { 468 expand(subResult, camelCasedName, ruleResult) 469 } 470 471 /* istanbul ignore else */ 472 if ((typeof subResult.value === 'number' || typeof subResult.value === 'string') 473 && !Object.values(util.SPLECIAL_ATTR).includes(camelCasedName)) { 474 ruleResult[camelCasedName] = subResult.value 475 } 476 if (subResult.log) { 477 subResult.log.line = declaration.position.start.line 478 subResult.log.column = declaration.position.start.column 479 ruleLog.push(subResult.log) 480 } 481 }) 482 rule.selectors.forEach(function (selector) { 483 if (selector.match(SELECTOR_MATCHER) || selector.match(DESCENDANT_SELECTOR_MATCHER)) { 484 var className = selector 485 486 // handle pseudo class 487 var pseudoIndex = className.indexOf(':') 488 if (pseudoIndex > -1) { 489 var pseudoCls = className.slice(pseudoIndex) 490 className = className.slice(0, pseudoIndex) 491 var pseudoRuleResult = {} 492 Object.keys(ruleResult).forEach(function (prop) { 493 pseudoRuleResult[prop + pseudoCls] = ruleResult[prop] 494 }) 495 ruleResult = pseudoRuleResult 496 } 497 // merge style 498 Object.keys(ruleResult).forEach(function (prop) { 499 // handle transition 500 if (prop.indexOf('transition') === 0 && prop !== 'transition') { 501 var realProp = prop.replace('transition', '') 502 realProp = realProp[0].toLowerCase() + realProp.slice(1) 503 mediaObj['@TRANSITION'] = mediaObj['@TRANSITION'] || {} 504 mediaObj['@TRANSITION'][className] = mediaObj['@TRANSITION'][className] || {} 505 mediaObj['@TRANSITION'][className][realProp] = ruleResult[prop] 506 } 507 mediaObj[className] = mediaObj[className] || {} 508 mediaObj[className][prop] = ruleResult[prop] 509 }) 510 } else { 511 log.push({ 512 line: rule.position.start.line, 513 column: rule.position.start.column, 514 reason: 'ERROR: The `' + selector + '` selector is not supported.' 515 }) 516 } 517 }) 518 log = log.concat(ruleLog) 519 } 520 }) 521 } 522 jsonStyle['@MEDIA'].push(mediaObj) 523 } 524 }) 525 } 526 527 done(err, {jsonStyle: jsonStyle, log: log}) 528} 529 530function parseImport(resourcePath, rule, jsonStyle, log) { 531 if(!resourcePath) { 532 return 533 } 534 const resourcePath_ = resourcePath 535 let importString = rule.import 536 let importPath 537 let mediaString = '' 538 let source = '' 539 if (importString.match(IMPORT_MATCHER)) { 540 let filePath = importString.match(/['"]([^()]+?)['"]/) 541 importPath = filePath[1] 542 mediaString = importString.replace(importPath, '').replace(/['"]/g, '') 543 } 544 if(/^(\.)|(\.\.)\//.test(importPath)) { 545 resourcePath = resourcePath.substring(0, resourcePath.lastIndexOf(path.sep) + 1); 546 importPath = path.resolve(resourcePath, importPath) 547 } 548 if (!fs.existsSync(importPath)) { 549 const fileSearch = findFile(importPath); 550 if (fileSearch.result == true) { 551 importPath = fileSearch.filePath; 552 } else { 553 writeErrorOption(); 554 log.push({ 555 line: rule.position.start.line, 556 column: rule.position.start.column, 557 reason: 'ERROR: no such file or directory, open ' + importPath 558 }); 559 return; 560 } 561 } 562 source = fs.readFileSync(importPath).toString(); 563 addPreviewCSS(importPath, resourcePath_); 564 if (mediaString.length !== 0) { 565 source = '@media ' + mediaString + '{\n' + source + '\n}' 566 } 567 parse(source, (err, obj) => { 568 if (err) { 569 throw(err) 570 } else { 571 jsonStyle = Object.assign(jsonStyle, obj.jsonStyle) 572 } 573 }, importPath) 574} 575 576function addPreviewCSS(importPath, resourcePath) { 577 importPath = path.join(importPath); 578 resourcePath = path.join(resourcePath); 579 if (fs.existsSync(process.env.watchCSSFiles)) { 580 const content = JSON.parse(fs.readFileSync(process.env.watchCSSFiles)); 581 if (content['entry'] && content['entry'][resourcePath]) { 582 content[importPath] = content[importPath] || []; 583 content[importPath].push(resourcePath); 584 content[importPath] = lodash.uniqWith(content[importPath], lodash.isEqual); 585 } else if (content[resourcePath]) { 586 content[importPath] = content[importPath] || []; 587 content[importPath].push(...content[resourcePath]); 588 content[importPath] = lodash.uniqWith(content[importPath], lodash.isEqual); 589 } 590 content["atime"] = content["atime"] || {}; 591 content["atime"][importPath] = fs.statSync(importPath).atime.toString(); 592 fs.writeFileSync(process.env.watchCSSFiles, JSON.stringify(content, null, 2)); 593 } 594} 595 596function findFile(importPath) { 597 const resultObject = { 598 result: false 599 }; 600 if (!importPath || !process.env.resolveModules) { 601 return resultObject; 602 } 603 try { 604 const modules = JSON.parse(process.env.resolveModules); 605 modules.forEach(item => { 606 if (fs.existsSync(item)) { 607 if (fs.existsSync(path.join(item, importPath))) { 608 resultObject.result = true; 609 resultObject.filePath = path.join(item, importPath); 610 return resultObject; 611 } 612 } else { 613 const resolveItem = path.resolve(__dirname, item); 614 if (fs.existsSync(resolveItem)) { 615 resultObject.result = true; 616 resultObject.filePath = path.join(resolveItem, importPath); 617 return resultObject; 618 } 619 } 620 }); 621 } catch (error) { 622 resultObject.result = false; 623 } 624 return resultObject; 625} 626 627function writeErrorOption() { 628 if (fs.existsSync(process.env.watchCSSFiles)) { 629 const content = JSON.parse(fs.readFileSync(process.env.watchCSSFiles)); 630 content['clear'] = true; 631 fs.writeFileSync(process.env.watchCSSFiles, JSON.stringify(content, null, 2)); 632 } 633} 634 635/** 636 * Validate a JSON Object and log errors & warnings 637 * 638 * @param {object} json 639 * @param {function} done which will be called with 640 * - err:Error 641 * - data.jsonStyle{}: `classname.propname.value`-like object 642 * - data.log[{reason}] 643 */ 644function validate(json, done) { 645 var log = [] 646 var err 647 648 try { 649 json = JSON.parse(JSON.stringify(json)) 650 } 651 catch (e) { 652 err = e 653 json = {} 654 } 655 656 Object.keys(json).forEach(function (selector) { 657 var declarations = json[selector] 658 659 Object.keys(declarations).forEach(function (name) { 660 var value = declarations[name] 661 var result = validateItem(name, value) 662 663 if (typeof result.value === 'number' || typeof result.value === 'string') { 664 declarations[name] = result.value 665 } 666 else { 667 delete declarations[name] 668 } 669 670 if (result.log) { 671 log.push(result.log) 672 } 673 }) 674 }) 675 676 done(err, { 677 jsonStyle: json, 678 log: log 679 }) 680} 681 682module.exports = { 683 parse: parse, 684 validate: validate, 685 validateItem: validateItem, 686 util: util, 687 expand: expand, 688 getUnit: getUnit, 689} 690 691