diff options
Diffstat (limited to 'node_modules/webpack/lib/css/CssParser.js')
| -rw-r--r-- | node_modules/webpack/lib/css/CssParser.js | 1049 |
1 files changed, 1049 insertions, 0 deletions
diff --git a/node_modules/webpack/lib/css/CssParser.js b/node_modules/webpack/lib/css/CssParser.js new file mode 100644 index 00000000..cf7633bf --- /dev/null +++ b/node_modules/webpack/lib/css/CssParser.js @@ -0,0 +1,1049 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ModuleDependencyWarning = require("../ModuleDependencyWarning"); +const { CSS_MODULE_TYPE_AUTO } = require("../ModuleTypeConstants"); +const Parser = require("../Parser"); +const WebpackError = require("../WebpackError"); +const ConstDependency = require("../dependencies/ConstDependency"); +const CssExportDependency = require("../dependencies/CssExportDependency"); +const CssImportDependency = require("../dependencies/CssImportDependency"); +const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifierDependency"); +const CssSelfLocalIdentifierDependency = require("../dependencies/CssSelfLocalIdentifierDependency"); +const CssUrlDependency = require("../dependencies/CssUrlDependency"); +const StaticExportsDependency = require("../dependencies/StaticExportsDependency"); +const { parseResource } = require("../util/identifier"); +const walkCssTokens = require("./walkCssTokens"); + +/** @typedef {import("../Parser").ParserState} ParserState */ +/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */ +/** @typedef {[number, number]} Range */ + +const CC_LEFT_CURLY = "{".charCodeAt(0); +const CC_RIGHT_CURLY = "}".charCodeAt(0); +const CC_COLON = ":".charCodeAt(0); +const CC_SLASH = "/".charCodeAt(0); +const CC_SEMICOLON = ";".charCodeAt(0); + +// https://www.w3.org/TR/css-syntax-3/#newline +// We don't have `preprocessing` stage, so we need specify all of them +const STRING_MULTILINE = /\\[\n\r\f]/g; +// https://www.w3.org/TR/css-syntax-3/#whitespace +const TRIM_WHITE_SPACES = /(^[ \t\n\r\f]*|[ \t\n\r\f]*$)/g; +const UNESCAPE = /\\([0-9a-fA-F]{1,6}[ \t\n\r\f]?|[\s\S])/g; +const IMAGE_SET_FUNCTION = /^(-\w+-)?image-set$/i; +const OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE = /^@(-\w+-)?keyframes$/; +const OPTIONALLY_VENDOR_PREFIXED_ANIMATION_PROPERTY = + /^(-\w+-)?animation(-name)?$/i; +const IS_MODULES = /\.module(s)?\.[^.]+$/i; + +/** + * @param {string} str url string + * @param {boolean} isString is url wrapped in quotes + * @returns {string} normalized url + */ +const normalizeUrl = (str, isString) => { + // Remove extra spaces and newlines: + // `url("im\ + // g.png")` + if (isString) { + str = str.replace(STRING_MULTILINE, ""); + } + + str = str + // Remove unnecessary spaces from `url(" img.png ")` + .replace(TRIM_WHITE_SPACES, "") + // Unescape + .replace(UNESCAPE, match => { + if (match.length > 2) { + return String.fromCharCode(Number.parseInt(match.slice(1).trim(), 16)); + } + return match[1]; + }); + + if (/^data:/i.test(str)) { + return str; + } + + if (str.includes("%")) { + // Convert `url('%2E/img.png')` -> `url('./img.png')` + try { + str = decodeURIComponent(str); + } catch (_err) { + // Ignore + } + } + + return str; +}; + +class LocConverter { + /** + * @param {string} input input + */ + constructor(input) { + this._input = input; + this.line = 1; + this.column = 0; + this.pos = 0; + } + + /** + * @param {number} pos position + * @returns {LocConverter} location converter + */ + get(pos) { + if (this.pos !== pos) { + if (this.pos < pos) { + const str = this._input.slice(this.pos, pos); + let i = str.lastIndexOf("\n"); + if (i === -1) { + this.column += str.length; + } else { + this.column = str.length - i - 1; + this.line++; + while (i > 0 && (i = str.lastIndexOf("\n", i - 1)) !== -1) + this.line++; + } + } else { + let i = this._input.lastIndexOf("\n", this.pos); + while (i >= pos) { + this.line--; + i = i > 0 ? this._input.lastIndexOf("\n", i - 1) : -1; + } + this.column = pos - i; + } + this.pos = pos; + } + return this; + } +} + +const CSS_MODE_TOP_LEVEL = 0; +const CSS_MODE_IN_BLOCK = 1; +const CSS_MODE_IN_AT_IMPORT = 2; +const CSS_MODE_AT_IMPORT_INVALID = 3; +const CSS_MODE_AT_NAMESPACE_INVALID = 4; + +class CssParser extends Parser { + constructor({ + allowModeSwitch = true, + defaultMode = "global", + namedExports = true + } = {}) { + super(); + this.allowModeSwitch = allowModeSwitch; + this.defaultMode = defaultMode; + this.namedExports = namedExports; + } + + /** + * @param {ParserState} state parser state + * @param {string} message warning message + * @param {LocConverter} locConverter location converter + * @param {number} start start offset + * @param {number} end end offset + */ + _emitWarning(state, message, locConverter, start, end) { + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(end); + + state.current.addWarning( + new ModuleDependencyWarning(state.module, new WebpackError(message), { + start: { line: sl, column: sc }, + end: { line: el, column: ec } + }) + ); + } + + /** + * @param {string | Buffer | PreparsedAst} source the source to parse + * @param {ParserState} state the parser state + * @returns {ParserState} the parser state + */ + parse(source, state) { + if (Buffer.isBuffer(source)) { + source = source.toString("utf-8"); + } else if (typeof source === "object") { + throw new Error("webpackAst is unexpected for the CssParser"); + } + if (source[0] === "\uFEFF") { + source = source.slice(1); + } + + const module = state.module; + + /** @type {string | undefined} */ + let oldDefaultMode; + + if ( + module.type === CSS_MODULE_TYPE_AUTO && + IS_MODULES.test( + parseResource(module.matchResource || module.resource).path + ) + ) { + oldDefaultMode = this.defaultMode; + + this.defaultMode = "local"; + } + + const locConverter = new LocConverter(source); + /** @type {Set<string>} */ + const declaredCssVariables = new Set(); + /** @type {number} */ + let scope = CSS_MODE_TOP_LEVEL; + /** @type {number} */ + let blockNestingLevel = 0; + /** @type {boolean} */ + let allowImportAtRule = true; + /** @type {"local" | "global" | undefined} */ + let modeData; + /** @type {[number, number] | undefined} */ + let lastIdentifier; + /** @type [string, number, number][] */ + const balanced = []; + /** @type {undefined | { start: number, url?: string, urlStart?: number, urlEnd?: number, layer?: string, layerStart?: number, layerEnd?: number, supports?: string, supportsStart?: number, supportsEnd?: number, inSupports?:boolean, media?: string }} */ + let importData; + /** @type {boolean} */ + let inAnimationProperty = false; + /** @type {boolean} */ + let isNextRulePrelude = true; + + /** + * @param {string} input input + * @param {number} pos position + * @returns {boolean} true, when next is nested syntax + */ + const isNextNestedSyntax = (input, pos) => { + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + + if (input[pos] === "}") { + return false; + } + + // According spec only identifier can be used as a property name + const isIdentifier = walkCssTokens.isIdentStartCodePoint( + input.charCodeAt(pos) + ); + + return !isIdentifier; + }; + /** + * @returns {boolean} true, when in local scope + */ + const isLocalMode = () => + modeData === "local" || + (this.defaultMode === "local" && modeData === undefined); + /** + * @param {string} chars characters + * @returns {(input: string, pos: number) => number} function to eat characters + */ + const eatUntil = chars => { + const charCodes = Array.from({ length: chars.length }, (_, i) => + chars.charCodeAt(i) + ); + const arr = Array.from( + { length: charCodes.reduce((a, b) => Math.max(a, b), 0) + 1 }, + () => false + ); + for (const cc of charCodes) { + arr[cc] = true; + } + return (input, pos) => { + for (;;) { + const cc = input.charCodeAt(pos); + if (cc < arr.length && arr[cc]) { + return pos; + } + pos++; + if (pos === input.length) return pos; + } + }; + }; + /** + * @param {string} input input + * @param {number} pos start position + * @param {(input: string, pos: number) => number} eater eater + * @returns {[number,string]} new position and text + */ + const eatText = (input, pos, eater) => { + let text = ""; + for (;;) { + if (input.charCodeAt(pos) === CC_SLASH) { + const newPos = walkCssTokens.eatComments(input, pos); + if (pos !== newPos) { + pos = newPos; + if (pos === input.length) break; + } else { + text += "/"; + pos++; + if (pos === input.length) break; + } + } + const newPos = eater(input, pos); + if (pos !== newPos) { + text += input.slice(pos, newPos); + pos = newPos; + } else { + break; + } + if (pos === input.length) break; + } + return [pos, text.trimEnd()]; + }; + const eatExportName = eatUntil(":};/"); + const eatExportValue = eatUntil("};/"); + /** + * @param {string} input input + * @param {number} pos start position + * @returns {number} position after parse + */ + const parseExports = (input, pos) => { + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + const cc = input.charCodeAt(pos); + if (cc !== CC_LEFT_CURLY) { + this._emitWarning( + state, + `Unexpected '${input[pos]}' at ${pos} during parsing of ':export' (expected '{')`, + locConverter, + pos, + pos + ); + return pos; + } + pos++; + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + for (;;) { + if (input.charCodeAt(pos) === CC_RIGHT_CURLY) break; + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + if (pos === input.length) return pos; + const start = pos; + let name; + [pos, name] = eatText(input, pos, eatExportName); + if (pos === input.length) return pos; + if (input.charCodeAt(pos) !== CC_COLON) { + this._emitWarning( + state, + `Unexpected '${input[pos]}' at ${pos} during parsing of export name in ':export' (expected ':')`, + locConverter, + start, + pos + ); + return pos; + } + pos++; + if (pos === input.length) return pos; + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + if (pos === input.length) return pos; + let value; + [pos, value] = eatText(input, pos, eatExportValue); + if (pos === input.length) return pos; + const cc = input.charCodeAt(pos); + if (cc === CC_SEMICOLON) { + pos++; + if (pos === input.length) return pos; + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + if (pos === input.length) return pos; + } else if (cc !== CC_RIGHT_CURLY) { + this._emitWarning( + state, + `Unexpected '${input[pos]}' at ${pos} during parsing of export value in ':export' (expected ';' or '}')`, + locConverter, + start, + pos + ); + return pos; + } + const dep = new CssExportDependency(name, value); + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(pos); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + } + pos++; + if (pos === input.length) return pos; + pos = walkCssTokens.eatWhiteLine(input, pos); + return pos; + }; + const eatPropertyName = eatUntil(":{};"); + /** + * @param {string} input input + * @param {number} pos name start position + * @param {number} end name end position + * @returns {number} position after handling + */ + const processLocalDeclaration = (input, pos, end) => { + modeData = undefined; + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + const propertyNameStart = pos; + const [propertyNameEnd, propertyName] = eatText( + input, + pos, + eatPropertyName + ); + if (input.charCodeAt(propertyNameEnd) !== CC_COLON) return end; + pos = propertyNameEnd + 1; + if (propertyName.startsWith("--")) { + // CSS Variable + const { line: sl, column: sc } = locConverter.get(propertyNameStart); + const { line: el, column: ec } = locConverter.get(propertyNameEnd); + const name = propertyName.slice(2); + const dep = new CssLocalIdentifierDependency( + name, + [propertyNameStart, propertyNameEnd], + "--" + ); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + declaredCssVariables.add(name); + } else if ( + !propertyName.startsWith("--") && + OPTIONALLY_VENDOR_PREFIXED_ANIMATION_PROPERTY.test(propertyName) + ) { + inAnimationProperty = true; + } + return pos; + }; + /** + * @param {string} input input + */ + const processDeclarationValueDone = input => { + if (inAnimationProperty && lastIdentifier) { + const { line: sl, column: sc } = locConverter.get(lastIdentifier[0]); + const { line: el, column: ec } = locConverter.get(lastIdentifier[1]); + const name = input.slice(lastIdentifier[0], lastIdentifier[1]); + const dep = new CssSelfLocalIdentifierDependency(name, lastIdentifier); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + lastIdentifier = undefined; + } + }; + const eatKeyframes = eatUntil("{};/"); + const eatNameInVar = eatUntil(",)};/"); + walkCssTokens(source, { + isSelector: () => isNextRulePrelude, + url: (input, start, end, contentStart, contentEnd) => { + const value = normalizeUrl( + input.slice(contentStart, contentEnd), + false + ); + + switch (scope) { + case CSS_MODE_IN_AT_IMPORT: { + // Do not parse URLs in `supports(...)` + if (importData.inSupports) { + break; + } + + if (importData.url) { + this._emitWarning( + state, + `Duplicate of 'url(...)' in '${input.slice( + importData.start, + end + )}'`, + locConverter, + start, + end + ); + + break; + } + + importData.url = value; + importData.urlStart = start; + importData.urlEnd = end; + break; + } + // Do not parse URLs in import between rules + case CSS_MODE_AT_NAMESPACE_INVALID: + case CSS_MODE_AT_IMPORT_INVALID: { + break; + } + case CSS_MODE_IN_BLOCK: { + // Ignore `url()`, `url('')` and `url("")`, they are valid by spec + if (value.length === 0) { + break; + } + + const dep = new CssUrlDependency(value, [start, end], "url"); + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(end); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + module.addCodeGenerationDependency(dep); + break; + } + } + return end; + }, + string: (input, start, end) => { + switch (scope) { + case CSS_MODE_IN_AT_IMPORT: { + const insideURLFunction = + balanced[balanced.length - 1] && + balanced[balanced.length - 1][0] === "url"; + + // Do not parse URLs in `supports(...)` and other strings if we already have a URL + if ( + importData.inSupports || + (!insideURLFunction && importData.url) + ) { + break; + } + + if (insideURLFunction && importData.url) { + this._emitWarning( + state, + `Duplicate of 'url(...)' in '${input.slice( + importData.start, + end + )}'`, + locConverter, + start, + end + ); + + break; + } + + importData.url = normalizeUrl( + input.slice(start + 1, end - 1), + true + ); + + if (!insideURLFunction) { + importData.urlStart = start; + importData.urlEnd = end; + } + + break; + } + case CSS_MODE_IN_BLOCK: { + // TODO move escaped parsing to tokenizer + const last = balanced[balanced.length - 1]; + + if ( + last && + (last[0].replace(/\\/g, "").toLowerCase() === "url" || + IMAGE_SET_FUNCTION.test(last[0].replace(/\\/g, ""))) + ) { + const value = normalizeUrl(input.slice(start + 1, end - 1), true); + + // Ignore `url()`, `url('')` and `url("")`, they are valid by spec + if (value.length === 0) { + break; + } + + const isUrl = last[0].replace(/\\/g, "").toLowerCase() === "url"; + const dep = new CssUrlDependency( + value, + [start, end], + isUrl ? "string" : "url" + ); + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(end); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + module.addCodeGenerationDependency(dep); + } + } + } + return end; + }, + atKeyword: (input, start, end) => { + const name = input.slice(start, end).toLowerCase(); + if (name === "@namespace") { + scope = CSS_MODE_AT_NAMESPACE_INVALID; + this._emitWarning( + state, + "'@namespace' is not supported in bundled CSS", + locConverter, + start, + end + ); + return end; + } else if (name === "@import") { + if (!allowImportAtRule) { + scope = CSS_MODE_AT_IMPORT_INVALID; + this._emitWarning( + state, + "Any '@import' rules must precede all other rules", + locConverter, + start, + end + ); + return end; + } + + scope = CSS_MODE_IN_AT_IMPORT; + importData = { start }; + } else if ( + this.allowModeSwitch && + OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE.test(name) + ) { + let pos = end; + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + if (pos === input.length) return pos; + const [newPos, name] = eatText(input, pos, eatKeyframes); + if (newPos === input.length) return newPos; + if (input.charCodeAt(newPos) !== CC_LEFT_CURLY) { + this._emitWarning( + state, + `Unexpected '${input[newPos]}' at ${newPos} during parsing of @keyframes (expected '{')`, + locConverter, + start, + end + ); + + return newPos; + } + if (isLocalMode()) { + const { line: sl, column: sc } = locConverter.get(pos); + const { line: el, column: ec } = locConverter.get(newPos); + const dep = new CssLocalIdentifierDependency(name, [pos, newPos]); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + } + pos = newPos; + return pos + 1; + } else if (this.allowModeSwitch && name === "@property") { + let pos = end; + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + if (pos === input.length) return pos; + const propertyNameStart = pos; + const [propertyNameEnd, propertyName] = eatText( + input, + pos, + eatKeyframes + ); + if (propertyNameEnd === input.length) return propertyNameEnd; + if (!propertyName.startsWith("--")) return propertyNameEnd; + if (input.charCodeAt(propertyNameEnd) !== CC_LEFT_CURLY) { + this._emitWarning( + state, + `Unexpected '${input[propertyNameEnd]}' at ${propertyNameEnd} during parsing of @property (expected '{')`, + locConverter, + start, + end + ); + + return propertyNameEnd; + } + const name = propertyName.slice(2); + declaredCssVariables.add(name); + if (isLocalMode()) { + const { line: sl, column: sc } = locConverter.get(pos); + const { line: el, column: ec } = locConverter.get(propertyNameEnd); + const dep = new CssLocalIdentifierDependency( + name, + [propertyNameStart, propertyNameEnd], + "--" + ); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + } + pos = propertyNameEnd; + return pos + 1; + } else if ( + name === "@media" || + name === "@supports" || + name === "@layer" || + name === "@container" + ) { + modeData = isLocalMode() ? "local" : "global"; + isNextRulePrelude = true; + return end; + } else if (this.allowModeSwitch) { + modeData = "global"; + isNextRulePrelude = false; + } + return end; + }, + semicolon: (input, start, end) => { + switch (scope) { + case CSS_MODE_IN_AT_IMPORT: { + const { start } = importData; + + if (importData.url === undefined) { + this._emitWarning( + state, + `Expected URL in '${input.slice(start, end)}'`, + locConverter, + start, + end + ); + importData = undefined; + scope = CSS_MODE_TOP_LEVEL; + return end; + } + if ( + importData.urlStart > importData.layerStart || + importData.urlStart > importData.supportsStart + ) { + this._emitWarning( + state, + `An URL in '${input.slice( + start, + end + )}' should be before 'layer(...)' or 'supports(...)'`, + locConverter, + start, + end + ); + importData = undefined; + scope = CSS_MODE_TOP_LEVEL; + return end; + } + if (importData.layerStart > importData.supportsStart) { + this._emitWarning( + state, + `The 'layer(...)' in '${input.slice( + start, + end + )}' should be before 'supports(...)'`, + locConverter, + start, + end + ); + importData = undefined; + scope = CSS_MODE_TOP_LEVEL; + return end; + } + + const semicolonPos = end; + end = walkCssTokens.eatWhiteLine(input, end); + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(end); + const lastEnd = + importData.supportsEnd || + importData.layerEnd || + importData.urlEnd || + start; + const pos = walkCssTokens.eatWhitespaceAndComments(input, lastEnd); + // Prevent to consider comments as a part of media query + if (pos !== semicolonPos - 1) { + importData.media = input.slice(lastEnd, semicolonPos - 1).trim(); + } + + const url = importData.url.trim(); + + if (url.length === 0) { + const dep = new ConstDependency("", [start, end]); + module.addPresentationalDependency(dep); + dep.setLoc(sl, sc, el, ec); + } else { + const dep = new CssImportDependency( + url, + [start, end], + importData.layer, + importData.supports, + importData.media && importData.media.length > 0 + ? importData.media + : undefined + ); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + } + + importData = undefined; + scope = CSS_MODE_TOP_LEVEL; + + break; + } + case CSS_MODE_AT_IMPORT_INVALID: + case CSS_MODE_AT_NAMESPACE_INVALID: { + scope = CSS_MODE_TOP_LEVEL; + + break; + } + case CSS_MODE_IN_BLOCK: { + if (this.allowModeSwitch) { + processDeclarationValueDone(input); + inAnimationProperty = false; + isNextRulePrelude = isNextNestedSyntax(input, end); + } + break; + } + } + return end; + }, + leftCurlyBracket: (input, start, end) => { + switch (scope) { + case CSS_MODE_TOP_LEVEL: { + allowImportAtRule = false; + scope = CSS_MODE_IN_BLOCK; + blockNestingLevel = 1; + + if (this.allowModeSwitch) { + isNextRulePrelude = isNextNestedSyntax(input, end); + } + + break; + } + case CSS_MODE_IN_BLOCK: { + blockNestingLevel++; + + if (this.allowModeSwitch) { + isNextRulePrelude = isNextNestedSyntax(input, end); + } + break; + } + } + return end; + }, + rightCurlyBracket: (input, start, end) => { + switch (scope) { + case CSS_MODE_IN_BLOCK: { + if (isLocalMode()) { + processDeclarationValueDone(input); + inAnimationProperty = false; + } + if (--blockNestingLevel === 0) { + scope = CSS_MODE_TOP_LEVEL; + + if (this.allowModeSwitch) { + isNextRulePrelude = true; + modeData = undefined; + } + } else if (this.allowModeSwitch) { + isNextRulePrelude = isNextNestedSyntax(input, end); + } + break; + } + } + return end; + }, + identifier: (input, start, end) => { + switch (scope) { + case CSS_MODE_IN_BLOCK: { + if (isLocalMode()) { + // Handle only top level values and not inside functions + if (inAnimationProperty && balanced.length === 0) { + lastIdentifier = [start, end]; + } else { + return processLocalDeclaration(input, start, end); + } + } + break; + } + case CSS_MODE_IN_AT_IMPORT: { + if (input.slice(start, end).toLowerCase() === "layer") { + importData.layer = ""; + importData.layerStart = start; + importData.layerEnd = end; + } + break; + } + } + return end; + }, + class: (input, start, end) => { + if (isLocalMode()) { + const name = input.slice(start + 1, end); + const dep = new CssLocalIdentifierDependency(name, [start + 1, end]); + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(end); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + } + + return end; + }, + id: (input, start, end) => { + if (isLocalMode()) { + const name = input.slice(start + 1, end); + const dep = new CssLocalIdentifierDependency(name, [start + 1, end]); + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(end); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + } + return end; + }, + function: (input, start, end) => { + let name = input.slice(start, end - 1); + + balanced.push([name, start, end]); + + if ( + scope === CSS_MODE_IN_AT_IMPORT && + name.toLowerCase() === "supports" + ) { + importData.inSupports = true; + } + + if (isLocalMode()) { + name = name.toLowerCase(); + + // Don't rename animation name when we have `var()` function + if (inAnimationProperty && balanced.length === 1) { + lastIdentifier = undefined; + } + + if (name === "var") { + const pos = walkCssTokens.eatWhitespaceAndComments(input, end); + if (pos === input.length) return pos; + const [newPos, name] = eatText(input, pos, eatNameInVar); + if (!name.startsWith("--")) return end; + const { line: sl, column: sc } = locConverter.get(pos); + const { line: el, column: ec } = locConverter.get(newPos); + const dep = new CssSelfLocalIdentifierDependency( + name.slice(2), + [pos, newPos], + "--", + declaredCssVariables + ); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + return newPos; + } + } + + return end; + }, + leftParenthesis: (input, start, end) => { + balanced.push(["(", start, end]); + + return end; + }, + rightParenthesis: (input, start, end) => { + const last = balanced[balanced.length - 1]; + const popped = balanced.pop(); + + if ( + this.allowModeSwitch && + popped && + (popped[0] === ":local" || popped[0] === ":global") + ) { + modeData = balanced[balanced.length - 1] + ? /** @type {"local" | "global"} */ + (balanced[balanced.length - 1][0]) + : undefined; + const dep = new ConstDependency("", [start, end]); + module.addPresentationalDependency(dep); + + return end; + } + + switch (scope) { + case CSS_MODE_IN_AT_IMPORT: { + if (last && last[0] === "url" && !importData.inSupports) { + importData.urlStart = last[1]; + importData.urlEnd = end; + } else if ( + last && + last[0].toLowerCase() === "layer" && + !importData.inSupports + ) { + importData.layer = input.slice(last[2], end - 1).trim(); + importData.layerStart = last[1]; + importData.layerEnd = end; + } else if (last && last[0].toLowerCase() === "supports") { + importData.supports = input.slice(last[2], end - 1).trim(); + importData.supportsStart = last[1]; + importData.supportsEnd = end; + importData.inSupports = false; + } + break; + } + } + + return end; + }, + pseudoClass: (input, start, end) => { + if (this.allowModeSwitch) { + const name = input.slice(start, end).toLowerCase(); + + if (name === ":global") { + modeData = "global"; + // Eat extra whitespace and comments + end = walkCssTokens.eatWhitespace(input, end); + const dep = new ConstDependency("", [start, end]); + module.addPresentationalDependency(dep); + return end; + } else if (name === ":local") { + modeData = "local"; + // Eat extra whitespace and comments + end = walkCssTokens.eatWhitespace(input, end); + const dep = new ConstDependency("", [start, end]); + module.addPresentationalDependency(dep); + return end; + } + + switch (scope) { + case CSS_MODE_TOP_LEVEL: { + if (name === ":export") { + const pos = parseExports(input, end); + const dep = new ConstDependency("", [start, pos]); + module.addPresentationalDependency(dep); + return pos; + } + break; + } + } + } + + return end; + }, + pseudoFunction: (input, start, end) => { + let name = input.slice(start, end - 1); + + balanced.push([name, start, end]); + + if (this.allowModeSwitch) { + name = name.toLowerCase(); + + if (name === ":global") { + modeData = "global"; + const dep = new ConstDependency("", [start, end]); + module.addPresentationalDependency(dep); + } else if (name === ":local") { + modeData = "local"; + const dep = new ConstDependency("", [start, end]); + module.addPresentationalDependency(dep); + } + } + + return end; + }, + comma: (input, start, end) => { + if (this.allowModeSwitch) { + // Reset stack for `:global .class :local .class-other` selector after + modeData = undefined; + + switch (scope) { + case CSS_MODE_IN_BLOCK: { + if (isLocalMode()) { + processDeclarationValueDone(input); + } + + break; + } + } + } + return end; + } + }); + + if (oldDefaultMode) { + this.defaultMode = oldDefaultMode; + } + + module.buildInfo.strict = true; + module.buildMeta.exportsType = this.namedExports ? "namespace" : "default"; + + if (!this.namedExports) { + module.buildMeta.defaultObject = "redirect"; + } + + module.addDependency(new StaticExportsDependency([], true)); + return state; + } +} + +module.exports = CssParser; |
