You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							549 lines
						
					
					
						
							15 KiB
						
					
					
				
			
		
		
	
	
							549 lines
						
					
					
						
							15 KiB
						
					
					
				'use strict'; | 
						|
 | 
						|
const postcss = require('postcss'); | 
						|
const selectorParser = require('postcss-selector-parser'); | 
						|
const valueParser = require('postcss-value-parser'); | 
						|
const { extractICSS } = require('icss-utils'); | 
						|
 | 
						|
const isSpacing = node => node.type === 'combinator' && node.value === ' '; | 
						|
 | 
						|
function getImportLocalAliases(icssImports) { | 
						|
  const localAliases = new Map(); | 
						|
  Object.keys(icssImports).forEach(key => { | 
						|
    Object.keys(icssImports[key]).forEach(prop => { | 
						|
      localAliases.set(prop, icssImports[key][prop]); | 
						|
    }); | 
						|
  }); | 
						|
  return localAliases; | 
						|
} | 
						|
 | 
						|
function maybeLocalizeValue(value, localAliasMap) { | 
						|
  if (localAliasMap.has(value)) return value; | 
						|
} | 
						|
 | 
						|
function normalizeNodeArray(nodes) { | 
						|
  const array = []; | 
						|
 | 
						|
  nodes.forEach(function(x) { | 
						|
    if (Array.isArray(x)) { | 
						|
      normalizeNodeArray(x).forEach(function(item) { | 
						|
        array.push(item); | 
						|
      }); | 
						|
    } else if (x) { | 
						|
      array.push(x); | 
						|
    } | 
						|
  }); | 
						|
 | 
						|
  if (array.length > 0 && isSpacing(array[array.length - 1])) { | 
						|
    array.pop(); | 
						|
  } | 
						|
  return array; | 
						|
} | 
						|
 | 
						|
function localizeNode(rule, mode, localAliasMap) { | 
						|
  const isScopePseudo = node => | 
						|
    node.value === ':local' || node.value === ':global'; | 
						|
  const isImportExportPseudo = node => | 
						|
    node.value === ':import' || node.value === ':export'; | 
						|
 | 
						|
  const transform = (node, context) => { | 
						|
    if (context.ignoreNextSpacing && !isSpacing(node)) { | 
						|
      throw new Error('Missing whitespace after ' + context.ignoreNextSpacing); | 
						|
    } | 
						|
    if (context.enforceNoSpacing && isSpacing(node)) { | 
						|
      throw new Error('Missing whitespace before ' + context.enforceNoSpacing); | 
						|
    } | 
						|
 | 
						|
    let newNodes; | 
						|
    switch (node.type) { | 
						|
      case 'root': { | 
						|
        let resultingGlobal; | 
						|
 | 
						|
        context.hasPureGlobals = false; | 
						|
 | 
						|
        newNodes = node.nodes.map(function(n) { | 
						|
          const nContext = { | 
						|
            global: context.global, | 
						|
            lastWasSpacing: true, | 
						|
            hasLocals: false, | 
						|
            explicit: false, | 
						|
          }; | 
						|
 | 
						|
          n = transform(n, nContext); | 
						|
 | 
						|
          if (typeof resultingGlobal === 'undefined') { | 
						|
            resultingGlobal = nContext.global; | 
						|
          } else if (resultingGlobal !== nContext.global) { | 
						|
            throw new Error( | 
						|
              'Inconsistent rule global/local result in rule "' + | 
						|
                node + | 
						|
                '" (multiple selectors must result in the same mode for the rule)' | 
						|
            ); | 
						|
          } | 
						|
 | 
						|
          if (!nContext.hasLocals) { | 
						|
            context.hasPureGlobals = true; | 
						|
          } | 
						|
 | 
						|
          return n; | 
						|
        }); | 
						|
 | 
						|
        context.global = resultingGlobal; | 
						|
 | 
						|
        node.nodes = normalizeNodeArray(newNodes); | 
						|
        break; | 
						|
      } | 
						|
      case 'selector': { | 
						|
        newNodes = node.map(childNode => transform(childNode, context)); | 
						|
 | 
						|
        node = node.clone(); | 
						|
        node.nodes = normalizeNodeArray(newNodes); | 
						|
        break; | 
						|
      } | 
						|
      case 'combinator': { | 
						|
        if (isSpacing(node)) { | 
						|
          if (context.ignoreNextSpacing) { | 
						|
            context.ignoreNextSpacing = false; | 
						|
            context.lastWasSpacing = false; | 
						|
            context.enforceNoSpacing = false; | 
						|
            return null; | 
						|
          } | 
						|
          context.lastWasSpacing = true; | 
						|
          return node; | 
						|
        } | 
						|
        break; | 
						|
      } | 
						|
      case 'pseudo': { | 
						|
        let childContext; | 
						|
        const isNested = !!node.length; | 
						|
        const isScoped = isScopePseudo(node); | 
						|
        const isImportExport = isImportExportPseudo(node); | 
						|
 | 
						|
        if (isImportExport) { | 
						|
          context.hasLocals = true; | 
						|
        // :local(.foo) | 
						|
        } else if (isNested) { | 
						|
          if (isScoped) { | 
						|
            if (node.nodes.length === 0) { | 
						|
              throw new Error(`${node.value}() can't be empty`); | 
						|
            } | 
						|
 | 
						|
            if (context.inside) { | 
						|
              throw new Error( | 
						|
                `A ${node.value} is not allowed inside of a ${ | 
						|
                  context.inside | 
						|
                }(...)` | 
						|
              ); | 
						|
            } | 
						|
 | 
						|
            childContext = { | 
						|
              global: node.value === ':global', | 
						|
              inside: node.value, | 
						|
              hasLocals: false, | 
						|
              explicit: true, | 
						|
            }; | 
						|
 | 
						|
            newNodes = node | 
						|
              .map(childNode => transform(childNode, childContext)) | 
						|
              .reduce((acc, next) => acc.concat(next.nodes), []); | 
						|
 | 
						|
            if (newNodes.length) { | 
						|
              const { before, after } = node.spaces; | 
						|
 | 
						|
              const first = newNodes[0]; | 
						|
              const last = newNodes[newNodes.length - 1]; | 
						|
 | 
						|
              first.spaces = { before, after: first.spaces.after }; | 
						|
              last.spaces = { before: last.spaces.before, after }; | 
						|
            } | 
						|
 | 
						|
            node = newNodes; | 
						|
 | 
						|
            break; | 
						|
          } else { | 
						|
            childContext = { | 
						|
              global: context.global, | 
						|
              inside: context.inside, | 
						|
              lastWasSpacing: true, | 
						|
              hasLocals: false, | 
						|
              explicit: context.explicit, | 
						|
            }; | 
						|
            newNodes = node.map(childNode => | 
						|
              transform(childNode, childContext) | 
						|
            ); | 
						|
 | 
						|
            node = node.clone(); | 
						|
            node.nodes = normalizeNodeArray(newNodes); | 
						|
 | 
						|
            if (childContext.hasLocals) { | 
						|
              context.hasLocals = true; | 
						|
            } | 
						|
          } | 
						|
          break; | 
						|
 | 
						|
          //:local .foo .bar | 
						|
        } else if (isScoped) { | 
						|
          if (context.inside) { | 
						|
            throw new Error( | 
						|
              `A ${node.value} is not allowed inside of a ${ | 
						|
                context.inside | 
						|
              }(...)` | 
						|
            ); | 
						|
          } | 
						|
 | 
						|
          const addBackSpacing = !!node.spaces.before; | 
						|
 | 
						|
          context.ignoreNextSpacing = context.lastWasSpacing | 
						|
            ? node.value | 
						|
            : false; | 
						|
 | 
						|
          context.enforceNoSpacing = context.lastWasSpacing | 
						|
            ? false | 
						|
            : node.value; | 
						|
 | 
						|
          context.global = node.value === ':global'; | 
						|
          context.explicit = true; | 
						|
 | 
						|
          // because this node has spacing that is lost when we remove it | 
						|
          // we make up for it by adding an extra combinator in since adding | 
						|
          // spacing on the parent selector doesn't work | 
						|
          return addBackSpacing | 
						|
            ? selectorParser.combinator({ value: ' ' }) | 
						|
            : null; | 
						|
        } | 
						|
        break; | 
						|
      } | 
						|
      case 'id': | 
						|
      case 'class': { | 
						|
        if (!node.value) { | 
						|
          throw new Error('Invalid class or id selector syntax'); | 
						|
        } | 
						|
 | 
						|
        if (context.global) { | 
						|
          break; | 
						|
        } | 
						|
 | 
						|
        const isImportedValue = localAliasMap.has(node.value); | 
						|
        const isImportedWithExplicitScope = isImportedValue && context.explicit; | 
						|
 | 
						|
        if (!isImportedValue || isImportedWithExplicitScope) { | 
						|
          const innerNode = node.clone(); | 
						|
          innerNode.spaces = { before: '', after: '' }; | 
						|
 | 
						|
          node = selectorParser.pseudo({ | 
						|
            value: ':local', | 
						|
            nodes: [innerNode], | 
						|
            spaces: node.spaces, | 
						|
          }); | 
						|
 | 
						|
          context.hasLocals = true; | 
						|
        } | 
						|
 | 
						|
        break; | 
						|
      } | 
						|
    } | 
						|
 | 
						|
    context.lastWasSpacing = false; | 
						|
    context.ignoreNextSpacing = false; | 
						|
    context.enforceNoSpacing = false; | 
						|
 | 
						|
    return node; | 
						|
  }; | 
						|
 | 
						|
  const rootContext = { | 
						|
    global: mode === 'global', | 
						|
    hasPureGlobals: false, | 
						|
  }; | 
						|
 | 
						|
  rootContext.selector = selectorParser(root => { | 
						|
    transform(root, rootContext); | 
						|
  }).processSync(rule, { updateSelector: false, lossless: true }); | 
						|
 | 
						|
  return rootContext; | 
						|
} | 
						|
 | 
						|
function localizeDeclNode(node, context) { | 
						|
  switch (node.type) { | 
						|
    case 'word': | 
						|
      if (context.localizeNextItem) { | 
						|
        if (!context.localAliasMap.has(node.value)) { | 
						|
          node.value = ':local(' + node.value + ')'; | 
						|
          context.localizeNextItem = false; | 
						|
        } | 
						|
      } | 
						|
      break; | 
						|
 | 
						|
    case 'function': | 
						|
      if ( | 
						|
        context.options && | 
						|
        context.options.rewriteUrl && | 
						|
        node.value.toLowerCase() === 'url' | 
						|
      ) { | 
						|
        node.nodes.map(nestedNode => { | 
						|
          if (nestedNode.type !== 'string' && nestedNode.type !== 'word') { | 
						|
            return; | 
						|
          } | 
						|
 | 
						|
          let newUrl = context.options.rewriteUrl( | 
						|
            context.global, | 
						|
            nestedNode.value | 
						|
          ); | 
						|
 | 
						|
          switch (nestedNode.type) { | 
						|
            case 'string': | 
						|
              if (nestedNode.quote === "'") { | 
						|
                newUrl = newUrl.replace(/(\\)/g, '\\$1').replace(/'/g, "\\'"); | 
						|
              } | 
						|
 | 
						|
              if (nestedNode.quote === '"') { | 
						|
                newUrl = newUrl.replace(/(\\)/g, '\\$1').replace(/"/g, '\\"'); | 
						|
              } | 
						|
 | 
						|
              break; | 
						|
            case 'word': | 
						|
              newUrl = newUrl.replace(/("|'|\)|\\)/g, '\\$1'); | 
						|
              break; | 
						|
          } | 
						|
 | 
						|
          nestedNode.value = newUrl; | 
						|
        }); | 
						|
      } | 
						|
      break; | 
						|
  } | 
						|
  return node; | 
						|
} | 
						|
 | 
						|
function isWordAFunctionArgument(wordNode, functionNode) { | 
						|
  return functionNode | 
						|
    ? functionNode.nodes.some( | 
						|
        functionNodeChild => | 
						|
          functionNodeChild.sourceIndex === wordNode.sourceIndex | 
						|
      ) | 
						|
    : false; | 
						|
} | 
						|
 | 
						|
function localizeAnimationShorthandDeclValues(decl, context) { | 
						|
  const validIdent = /^-?[_a-z][_a-z0-9-]*$/i; | 
						|
 | 
						|
  /* | 
						|
  The spec defines some keywords that you can use to describe properties such as the timing | 
						|
  function. These are still valid animation names, so as long as there is a property that accepts | 
						|
  a keyword, it is given priority. Only when all the properties that can take a keyword are | 
						|
  exhausted can the animation name be set to the keyword. I.e. | 
						|
 | 
						|
  animation: infinite infinite; | 
						|
 | 
						|
  The animation will repeat an infinite number of times from the first argument, and will have an | 
						|
  animation name of infinite from the second. | 
						|
  */ | 
						|
  const animationKeywords = { | 
						|
    $alternate: 1, | 
						|
    '$alternate-reverse': 1, | 
						|
    $backwards: 1, | 
						|
    $both: 1, | 
						|
    $ease: 1, | 
						|
    '$ease-in': 1, | 
						|
    '$ease-in-out': 1, | 
						|
    '$ease-out': 1, | 
						|
    $forwards: 1, | 
						|
    $infinite: 1, | 
						|
    $linear: 1, | 
						|
    $none: Infinity, // No matter how many times you write none, it will never be an animation name | 
						|
    $normal: 1, | 
						|
    $paused: 1, | 
						|
    $reverse: 1, | 
						|
    $running: 1, | 
						|
    '$step-end': 1, | 
						|
    '$step-start': 1, | 
						|
    $initial: Infinity, | 
						|
    $inherit: Infinity, | 
						|
    $unset: Infinity, | 
						|
  }; | 
						|
 | 
						|
  const didParseAnimationName = false; | 
						|
  let parsedAnimationKeywords = {}; | 
						|
  let stepsFunctionNode = null; | 
						|
  const valueNodes = valueParser(decl.value).walk(node => { | 
						|
    /* If div-token appeared (represents as comma ','), a possibility of an animation-keywords should be reflesh. */ | 
						|
    if (node.type === 'div') { | 
						|
      parsedAnimationKeywords = {}; | 
						|
    } | 
						|
    if (node.type === 'function' && node.value.toLowerCase() === 'steps') { | 
						|
      stepsFunctionNode = node; | 
						|
    } | 
						|
    const value = | 
						|
      node.type === 'word' && !isWordAFunctionArgument(node, stepsFunctionNode) | 
						|
        ? node.value.toLowerCase() | 
						|
        : null; | 
						|
 | 
						|
    let shouldParseAnimationName = false; | 
						|
 | 
						|
    if (!didParseAnimationName && value && validIdent.test(value)) { | 
						|
      if ('$' + value in animationKeywords) { | 
						|
        parsedAnimationKeywords['$' + value] = | 
						|
          '$' + value in parsedAnimationKeywords | 
						|
            ? parsedAnimationKeywords['$' + value] + 1 | 
						|
            : 0; | 
						|
 | 
						|
        shouldParseAnimationName = | 
						|
          parsedAnimationKeywords['$' + value] >= | 
						|
          animationKeywords['$' + value]; | 
						|
      } else { | 
						|
        shouldParseAnimationName = true; | 
						|
      } | 
						|
    } | 
						|
 | 
						|
    const subContext = { | 
						|
      options: context.options, | 
						|
      global: context.global, | 
						|
      localizeNextItem: shouldParseAnimationName && !context.global, | 
						|
      localAliasMap: context.localAliasMap, | 
						|
    }; | 
						|
    return localizeDeclNode(node, subContext); | 
						|
  }); | 
						|
 | 
						|
  decl.value = valueNodes.toString(); | 
						|
} | 
						|
 | 
						|
function localizeDeclValues(localize, decl, context) { | 
						|
  const valueNodes = valueParser(decl.value); | 
						|
  valueNodes.walk((node, index, nodes) => { | 
						|
    const subContext = { | 
						|
      options: context.options, | 
						|
      global: context.global, | 
						|
      localizeNextItem: localize && !context.global, | 
						|
      localAliasMap: context.localAliasMap, | 
						|
    }; | 
						|
    nodes[index] = localizeDeclNode(node, subContext); | 
						|
  }); | 
						|
  decl.value = valueNodes.toString(); | 
						|
} | 
						|
 | 
						|
function localizeDecl(decl, context) { | 
						|
  const isAnimation = /animation$/i.test(decl.prop); | 
						|
 | 
						|
  if (isAnimation) { | 
						|
    return localizeAnimationShorthandDeclValues(decl, context); | 
						|
  } | 
						|
 | 
						|
  const isAnimationName = /animation(-name)?$/i.test(decl.prop); | 
						|
 | 
						|
  if (isAnimationName) { | 
						|
    return localizeDeclValues(true, decl, context); | 
						|
  } | 
						|
 | 
						|
  const hasUrl = /url\(/i.test(decl.value); | 
						|
 | 
						|
  if (hasUrl) { | 
						|
    return localizeDeclValues(false, decl, context); | 
						|
  } | 
						|
} | 
						|
 | 
						|
module.exports = postcss.plugin('postcss-modules-local-by-default', function( | 
						|
  options | 
						|
) { | 
						|
  if (typeof options !== 'object') { | 
						|
    options = {}; // If options is undefined or not an object the plugin fails | 
						|
  } | 
						|
 | 
						|
  if (options && options.mode) { | 
						|
    if ( | 
						|
      options.mode !== 'global' && | 
						|
      options.mode !== 'local' && | 
						|
      options.mode !== 'pure' | 
						|
    ) { | 
						|
      throw new Error( | 
						|
        'options.mode must be either "global", "local" or "pure" (default "local")' | 
						|
      ); | 
						|
    } | 
						|
  } | 
						|
 | 
						|
  const pureMode = options && options.mode === 'pure'; | 
						|
  const globalMode = options && options.mode === 'global'; | 
						|
 | 
						|
  return function(css) { | 
						|
    const { icssImports } = extractICSS(css, false); | 
						|
    const localAliasMap = getImportLocalAliases(icssImports); | 
						|
 | 
						|
    css.walkAtRules(function(atrule) { | 
						|
      if (/keyframes$/i.test(atrule.name)) { | 
						|
        const globalMatch = /^\s*:global\s*\((.+)\)\s*$/.exec(atrule.params); | 
						|
        const localMatch = /^\s*:local\s*\((.+)\)\s*$/.exec(atrule.params); | 
						|
        let globalKeyframes = globalMode; | 
						|
        if (globalMatch) { | 
						|
          if (pureMode) { | 
						|
            throw atrule.error( | 
						|
              '@keyframes :global(...) is not allowed in pure mode' | 
						|
            ); | 
						|
          } | 
						|
          atrule.params = globalMatch[1]; | 
						|
          globalKeyframes = true; | 
						|
        } else if (localMatch) { | 
						|
          atrule.params = localMatch[0]; | 
						|
          globalKeyframes = false; | 
						|
        } else if (!globalMode) { | 
						|
          if (atrule.params && !localAliasMap.has(atrule.params)) | 
						|
            atrule.params = ':local(' + atrule.params + ')'; | 
						|
        } | 
						|
        atrule.walkDecls(function(decl) { | 
						|
          localizeDecl(decl, { | 
						|
            localAliasMap, | 
						|
            options: options, | 
						|
            global: globalKeyframes, | 
						|
          }); | 
						|
        }); | 
						|
      } else if (atrule.nodes) { | 
						|
        atrule.nodes.forEach(function(decl) { | 
						|
          if (decl.type === 'decl') { | 
						|
            localizeDecl(decl, { | 
						|
              localAliasMap, | 
						|
              options: options, | 
						|
              global: globalMode, | 
						|
            }); | 
						|
          } | 
						|
        }); | 
						|
      } | 
						|
    }); | 
						|
 | 
						|
    css.walkRules(function(rule) { | 
						|
      if ( | 
						|
        rule.parent && | 
						|
        rule.parent.type === 'atrule' && | 
						|
        /keyframes$/i.test(rule.parent.name) | 
						|
      ) { | 
						|
        // ignore keyframe rules | 
						|
        return; | 
						|
      } | 
						|
 | 
						|
      if ( | 
						|
        rule.nodes && | 
						|
        rule.selector.slice(0, 2) === '--' && | 
						|
        rule.selector.slice(-1) === ':' | 
						|
      ) { | 
						|
        // ignore custom property set | 
						|
        return; | 
						|
      } | 
						|
 | 
						|
      const context = localizeNode(rule, options.mode, localAliasMap); | 
						|
 | 
						|
      context.options = options; | 
						|
      context.localAliasMap = localAliasMap; | 
						|
 | 
						|
      if (pureMode && context.hasPureGlobals) { | 
						|
        throw rule.error( | 
						|
          'Selector "' + | 
						|
            rule.selector + | 
						|
            '" is not pure ' + | 
						|
            '(pure selectors must contain at least one local class or id)' | 
						|
        ); | 
						|
      } | 
						|
 | 
						|
      rule.selector = context.selector; | 
						|
 | 
						|
      // Less-syntax mixins parse as rules with no nodes | 
						|
      if (rule.nodes) { | 
						|
        rule.nodes.forEach(decl => localizeDecl(decl, context)); | 
						|
      } | 
						|
    }); | 
						|
  }; | 
						|
});
 | 
						|
 |