• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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