webpack/lib/css/CssParser.js

1105 lines
31 KiB
JavaScript
Raw Normal View History

2021-11-30 19:55:51 +08:00
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
2024-10-16 22:42:26 +08:00
const vm = require("vm");
const CommentCompilationWarning = require("../CommentCompilationWarning");
2023-05-02 18:12:41 +08:00
const ModuleDependencyWarning = require("../ModuleDependencyWarning");
2022-12-16 20:56:14 +08:00
const { CSS_MODULE_TYPE_AUTO } = require("../ModuleTypeConstants");
2021-11-30 19:55:51 +08:00
const Parser = require("../Parser");
2024-10-16 22:42:26 +08:00
const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning");
2023-05-02 18:12:41 +08:00
const WebpackError = require("../WebpackError");
2021-12-14 23:02:26 +08:00
const ConstDependency = require("../dependencies/ConstDependency");
const CssExportDependency = require("../dependencies/CssExportDependency");
2021-12-01 20:27:00 +08:00
const CssImportDependency = require("../dependencies/CssImportDependency");
2021-12-15 15:34:31 +08:00
const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifierDependency");
const CssSelfLocalIdentifierDependency = require("../dependencies/CssSelfLocalIdentifierDependency");
2021-12-01 21:15:19 +08:00
const CssUrlDependency = require("../dependencies/CssUrlDependency");
2021-11-30 19:55:51 +08:00
const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
2024-10-16 22:42:26 +08:00
const binarySearchBounds = require("../util/binarySearchBounds");
2023-06-11 21:33:10 +08:00
const { parseResource } = require("../util/identifier");
2024-10-16 22:42:26 +08:00
const {
webpackCommentRegExp,
createMagicCommentContext
} = require("../util/magicComment");
2021-11-30 19:55:51 +08:00
const walkCssTokens = require("./walkCssTokens");
2024-10-01 03:05:27 +08:00
/** @typedef {import("../Module").BuildInfo} BuildInfo */
/** @typedef {import("../Module").BuildMeta} BuildMeta */
2021-11-30 19:55:51 +08:00
/** @typedef {import("../Parser").ParserState} ParserState */
/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
2024-10-01 03:05:27 +08:00
2023-05-22 04:31:30 +08:00
/** @typedef {[number, number]} Range */
2024-10-16 22:42:26 +08:00
/** @typedef {{ line: number, column: number }} Position */
/** @typedef {{ value: string, range: Range, loc: { start: Position, end: Position } }} Comment */
2023-05-22 04:31:30 +08:00
2021-12-14 23:02:26 +08:00
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);
2024-10-09 17:09:36 +08:00
const CC_LEFT_PARENTHESIS = "(".charCodeAt(0);
2021-12-14 23:02:26 +08:00
2023-04-15 00:19:27 +08:00
// 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;
2023-04-13 07:27:04 +08:00
const UNESCAPE = /\\([0-9a-fA-F]{1,6}[ \t\n\r\f]?|[\s\S])/g;
2024-10-09 14:01:49 +08:00
const IMAGE_SET_FUNCTION = /^(-\w+-)?image-set$/i;
2023-04-17 00:43:06 +08:00
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;
2023-04-13 07:27:04 +08:00
2023-04-29 02:50:41 +08:00
/**
* @param {string} str url string
* @param {boolean} isString is url wrapped in quotes
* @returns {string} normalized url
*/
2023-04-13 07:27:04 +08:00
const normalizeUrl = (str, isString) => {
2023-04-13 07:36:01 +08:00
// Remove extra spaces and newlines:
2023-04-13 07:27:04 +08:00
// `url("im\
// g.png")`
if (isString) {
str = str.replace(STRING_MULTILINE, "");
}
2023-04-13 07:36:01 +08:00
str = str
// Remove unnecessary spaces from `url(" img.png ")`
.replace(TRIM_WHITE_SPACES, "")
// Unescape
.replace(UNESCAPE, match => {
if (match.length > 2) {
2024-08-02 02:36:27 +08:00
return String.fromCharCode(Number.parseInt(match.slice(1).trim(), 16));
2023-04-13 07:36:01 +08:00
}
2024-07-31 04:21:27 +08:00
return match[1];
2023-04-13 07:36:01 +08:00
});
if (/^data:/i.test(str)) {
return str;
}
if (str.includes("%")) {
2023-04-13 09:08:07 +08:00
// Convert `url('%2E/img.png')` -> `url('./img.png')`
2023-04-13 07:36:01 +08:00
try {
str = decodeURIComponent(str);
2024-07-31 15:37:05 +08:00
} catch (_err) {
2023-04-13 07:36:01 +08:00
// Ignore
}
}
return str;
2021-11-30 19:55:51 +08:00
};
2021-12-01 21:15:19 +08:00
class LocConverter {
2023-04-29 02:50:41 +08:00
/**
* @param {string} input input
*/
2021-12-01 21:15:19 +08:00
constructor(input) {
this._input = input;
this.line = 1;
this.column = 0;
this.pos = 0;
}
2023-04-29 02:50:41 +08:00
/**
* @param {number} pos position
* @returns {LocConverter} location converter
*/
2021-12-01 21:15:19 +08:00
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++;
2021-12-03 02:29:55 +08:00
while (i > 0 && (i = str.lastIndexOf("\n", i - 1)) !== -1)
this.line++;
2021-12-01 21:15:19 +08:00
}
} else {
let i = this._input.lastIndexOf("\n", this.pos);
while (i >= pos) {
this.line--;
2021-12-03 02:29:55 +08:00
i = i > 0 ? this._input.lastIndexOf("\n", i - 1) : -1;
2021-12-01 21:15:19 +08:00
}
this.column = pos - i;
}
this.pos = pos;
}
return this;
}
}
2024-10-16 22:42:26 +08:00
const EMPTY_COMMENT_OPTIONS = {
options: null,
errors: null
};
2021-12-01 20:27:00 +08:00
const CSS_MODE_TOP_LEVEL = 0;
2023-05-05 07:49:09 +08:00
const CSS_MODE_IN_BLOCK = 1;
2021-12-01 20:27:00 +08:00
2021-11-30 19:55:51 +08:00
class CssParser extends Parser {
/**
* @param {object} options options
* @param {("pure" | "global" | "local" | "auto")=} options.defaultMode default mode
* @param {boolean=} options.namedExports is named exports
*/
constructor({ defaultMode = "pure", namedExports = true } = {}) {
2021-11-30 19:55:51 +08:00
super();
2021-12-15 15:34:31 +08:00
this.defaultMode = defaultMode;
2024-01-13 18:23:30 +08:00
this.namedExports = namedExports;
2024-10-16 22:42:26 +08:00
/** @type {Comment[] | undefined} */
this.comments = undefined;
this.magicCommentContext = createMagicCommentContext();
2021-11-30 19:55:51 +08:00
}
2023-05-02 08:24:36 +08:00
/**
* @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 }
})
);
}
2021-11-30 19:55:51 +08:00
/**
* @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");
}
2024-08-02 02:36:27 +08:00
if (source[0] === "\uFEFF") {
2021-11-30 19:55:51 +08:00
source = source.slice(1);
}
let mode = this.defaultMode;
2023-06-21 04:40:47 +08:00
const module = state.module;
2023-06-21 04:40:47 +08:00
2022-12-16 20:56:14 +08:00
if (
mode === "auto" &&
2022-12-16 20:56:14 +08:00
module.type === CSS_MODULE_TYPE_AUTO &&
2023-06-11 21:33:10 +08:00
IS_MODULES.test(
parseResource(module.matchResource || module.resource).path
)
2022-12-16 20:56:14 +08:00
) {
mode = "local";
2022-12-16 20:56:14 +08:00
}
const isModules = mode === "global" || mode === "local";
2021-12-01 21:15:19 +08:00
const locConverter = new LocConverter(source);
2024-10-16 00:04:49 +08:00
2023-04-29 02:50:41 +08:00
/** @type {number} */
2023-05-05 07:53:57 +08:00
let scope = CSS_MODE_TOP_LEVEL;
2023-05-02 08:24:36 +08:00
/** @type {boolean} */
let allowImportAtRule = true;
2023-04-14 06:52:50 +08:00
/** @type [string, number, number][] */
2024-07-31 04:09:42 +08:00
const balanced = [];
2024-10-16 00:04:49 +08:00
2023-05-05 04:59:30 +08:00
/** @type {boolean} */
2024-10-16 00:04:49 +08:00
let isNextRulePrelude = isModules;
/** @type {number} */
let blockNestingLevel = 0;
/** @type {"local" | "global" | undefined} */
let modeData;
2023-05-05 06:13:27 +08:00
/** @type {boolean} */
2024-10-16 00:04:49 +08:00
let inAnimationProperty = false;
/** @type {Set<string>} */
const declaredCssVariables = new Set();
2024-10-16 03:32:17 +08:00
/** @type {[number, number, boolean] | undefined} */
2024-10-16 00:04:49 +08:00
let lastIdentifier;
2023-04-14 04:25:46 +08:00
2023-05-05 09:04:20 +08:00
/**
* @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;
};
2023-05-05 02:28:09 +08:00
/**
2023-05-05 07:53:57 +08:00
* @returns {boolean} true, when in local scope
2023-05-05 02:28:09 +08:00
*/
const isLocalMode = () =>
modeData === "local" || (mode === "local" && modeData === undefined);
2024-10-16 03:32:17 +08:00
2023-05-05 05:38:41 +08:00
/**
* @param {string} input input
* @param {number} pos start position
* @param {(input: string, pos: number) => number} eater eater
* @returns {[number,string]} new position and text
*/
2021-12-14 23:02:26 +08:00
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;
}
2022-08-31 17:56:03 +08:00
return [pos, text.trimEnd()];
2021-12-14 23:02:26 +08:00
};
2024-10-16 03:32:17 +08:00
const eatExportName = walkCssTokens.eatUntil(":};/");
const eatExportValue = walkCssTokens.eatUntil("};/");
2023-05-05 05:38:41 +08:00
/**
* @param {string} input input
* @param {number} pos start position
* @returns {number} position after parse
*/
2021-12-14 23:02:26 +08:00
const parseExports = (input, pos) => {
pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
const cc = input.charCodeAt(pos);
2023-05-02 17:15:52 +08:00
if (cc !== CC_LEFT_CURLY) {
this._emitWarning(
state,
`Unexpected '${input[pos]}' at ${pos} during parsing of ':export' (expected '{')`,
locConverter,
pos,
pos
2021-12-14 23:02:26 +08:00
);
2023-05-02 17:15:52 +08:00
return pos;
}
2021-12-14 23:02:26 +08:00
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;
2024-07-31 04:09:42 +08:00
const start = pos;
2021-12-14 23:02:26 +08:00
let name;
[pos, name] = eatText(input, pos, eatExportName);
if (pos === input.length) return pos;
if (input.charCodeAt(pos) !== CC_COLON) {
2023-05-02 17:15:52 +08:00
this._emitWarning(
state,
`Unexpected '${input[pos]}' at ${pos} during parsing of export name in ':export' (expected ':')`,
locConverter,
start,
pos
2021-12-14 23:02:26 +08:00
);
2023-05-02 17:15:52 +08:00
return pos;
2021-12-14 23:02:26 +08:00
}
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) {
2023-05-02 17:15:52 +08:00
this._emitWarning(
state,
`Unexpected '${input[pos]}' at ${pos} during parsing of export value in ':export' (expected ';' or '}')`,
locConverter,
start,
pos
2021-12-14 23:02:26 +08:00
);
2023-05-02 17:15:52 +08:00
return pos;
2021-12-14 23:02:26 +08:00
}
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;
2023-05-01 22:46:47 +08:00
pos = walkCssTokens.eatWhiteLine(input, pos);
2021-12-14 23:02:26 +08:00
return pos;
};
2024-10-16 03:32:17 +08:00
const eatPropertyName = walkCssTokens.eatUntil(":{};");
2023-05-05 05:27:59 +08:00
/**
* @param {string} input input
* @param {number} pos name start position
* @param {number} end name end position
* @returns {number} position after handling
*/
2023-05-05 03:40:00 +08:00
const processLocalDeclaration = (input, pos, end) => {
modeData = undefined;
pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
const propertyNameStart = pos;
const [propertyNameEnd, propertyName] = eatText(
input,
pos,
eatPropertyName
);
2023-05-05 03:40:00 +08:00
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);
2023-04-17 00:43:06 +08:00
} else if (
OPTIONALLY_VENDOR_PREFIXED_ANIMATION_PROPERTY.test(propertyName)
) {
2023-05-05 04:59:30 +08:00
inAnimationProperty = true;
}
return pos;
};
2023-05-05 04:59:30 +08:00
/**
* @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]);
2024-10-16 03:32:17 +08:00
const name = lastIdentifier[2]
? input.slice(lastIdentifier[0], lastIdentifier[1])
2024-10-16 11:04:30 +08:00
: input.slice(lastIdentifier[0] + 1, lastIdentifier[1] - 1);
2024-10-16 03:32:17 +08:00
const dep = new CssSelfLocalIdentifierDependency(name, [
lastIdentifier[0],
lastIdentifier[1]
]);
dep.setLoc(sl, sc, el, ec);
module.addDependency(dep);
2023-05-05 04:59:30 +08:00
lastIdentifier = undefined;
}
};
2024-10-16 03:32:17 +08:00
const eatUntilSemi = walkCssTokens.eatUntil(";");
const eatUntilLeftCurly = walkCssTokens.eatUntil("{");
2024-10-16 22:57:45 +08:00
/**
* @param {string} input input
* @param {number} start start
* @param {number} end end
* @returns {number} end
*/
const comment = (input, start, end) => {
if (!this.comments) this.comments = [];
const { line: sl, column: sc } = locConverter.get(start);
const { line: el, column: ec } = locConverter.get(end);
/** @type {Comment} */
const comment = {
value: input.slice(start + 2, end - 2),
range: [start, end],
loc: {
start: { line: sl, column: sc },
end: { line: el, column: ec }
}
};
this.comments.push(comment);
return end;
};
2024-10-16 22:42:26 +08:00
2024-10-16 22:57:45 +08:00
walkCssTokens(source, {
comment,
2024-10-09 14:21:00 +08:00
leftCurlyBracket: (input, start, end) => {
switch (scope) {
case CSS_MODE_TOP_LEVEL: {
allowImportAtRule = false;
scope = CSS_MODE_IN_BLOCK;
2024-10-16 00:04:49 +08:00
if (isModules) {
blockNestingLevel = 1;
isNextRulePrelude = isNextNestedSyntax(input, end);
}
2024-10-09 14:21:00 +08:00
break;
}
case CSS_MODE_IN_BLOCK: {
2024-10-16 00:04:49 +08:00
if (isModules) {
blockNestingLevel++;
isNextRulePrelude = isNextNestedSyntax(input, end);
}
2024-10-09 14:21:00 +08:00
break;
}
}
return end;
},
rightCurlyBracket: (input, start, end) => {
switch (scope) {
case CSS_MODE_IN_BLOCK: {
if (--blockNestingLevel === 0) {
scope = CSS_MODE_TOP_LEVEL;
if (isModules) {
2024-10-16 00:04:49 +08:00
isNextRulePrelude = true;
2024-10-09 14:21:00 +08:00
modeData = undefined;
}
2024-10-16 00:04:49 +08:00
} else if (isModules) {
2024-10-16 11:04:30 +08:00
if (isLocalMode()) {
processDeclarationValueDone(input);
inAnimationProperty = false;
}
2024-10-09 14:21:00 +08:00
isNextRulePrelude = isNextNestedSyntax(input, end);
}
break;
}
}
return end;
},
2023-05-02 08:24:36 +08:00
url: (input, start, end, contentStart, contentEnd) => {
2024-07-31 04:09:42 +08:00
const value = normalizeUrl(
input.slice(contentStart, contentEnd),
false
);
2024-10-16 03:32:17 +08:00
// Ignore `url()`, `url('')` and `url("")`, they are valid by spec
if (value.length === 0) return end;
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);
2021-12-01 20:27:00 +08:00
return end;
},
2024-10-16 03:32:17 +08:00
string: (_input, start, end) => {
2023-05-05 07:53:57 +08:00
switch (scope) {
case CSS_MODE_IN_BLOCK: {
2024-10-16 03:32:17 +08:00
if (inAnimationProperty && balanced.length === 0) {
lastIdentifier = [start, end, false];
2023-04-14 04:25:46 +08:00
}
}
2021-12-01 20:27:00 +08:00
}
return end;
},
atKeyword: (input, start, end) => {
2023-04-06 04:06:29 +08:00
const name = input.slice(start, end).toLowerCase();
2024-10-16 03:32:17 +08:00
switch (name) {
case "@namespace": {
2023-05-04 19:50:11 +08:00
this._emitWarning(
state,
2024-10-16 03:32:17 +08:00
"'@namespace' is not supported in bundled CSS",
2023-05-04 19:50:11 +08:00
locConverter,
start,
end
);
2024-10-16 03:32:17 +08:00
return eatUntilSemi(input, start);
2023-05-04 19:50:11 +08:00
}
2024-10-16 03:32:17 +08:00
case "@import": {
if (!allowImportAtRule) {
2023-05-02 17:15:52 +08:00
this._emitWarning(
state,
2024-10-16 03:32:17 +08:00
"Any '@import' rules must precede all other rules",
2023-05-02 17:15:52 +08:00
locConverter,
2023-05-20 00:05:14 +08:00
start,
2023-05-19 22:37:38 +08:00
end
2023-04-26 04:31:35 +08:00
);
2023-05-19 22:37:38 +08:00
return end;
}
2024-10-16 03:32:17 +08:00
2024-10-16 22:57:45 +08:00
const tokens = walkCssTokens.eatImportTokens(input, end, {
comment
});
2024-10-16 03:32:17 +08:00
if (!tokens[3]) return end;
const semi = tokens[3][1];
if (!tokens[0]) {
2023-05-19 22:37:38 +08:00
this._emitWarning(
state,
2024-10-16 03:32:17 +08:00
`Expected URL in '${input.slice(start, semi)}'`,
2023-05-19 22:37:38 +08:00
locConverter,
2023-05-20 00:05:14 +08:00
start,
2024-10-16 03:32:17 +08:00
semi
2023-05-19 22:37:38 +08:00
);
return end;
}
2024-10-16 03:32:17 +08:00
const urlToken = tokens[0];
const url = normalizeUrl(
input.slice(urlToken[2], urlToken[3]),
true
);
const newline = walkCssTokens.eatWhiteLine(input, semi);
2023-05-19 19:44:13 +08:00
2024-10-16 22:57:45 +08:00
const { options, errors: commentErrors } = this.parseCommentOptions(
[end, urlToken[1]]
);
if (commentErrors) {
for (const e of commentErrors) {
const { comment } = e;
state.module.addWarning(
new CommentCompilationWarning(
`Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
comment.loc
)
);
}
}
if (options && options.webpackIgnore !== undefined) {
if (typeof options.webpackIgnore !== "boolean") {
const { line: sl, column: sc } = locConverter.get(start);
const { line: el, column: ec } = locConverter.get(newline);
state.module.addWarning(
new UnsupportedFeatureWarning(
`\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
{
start: { line: sl, column: sc },
end: { line: el, column: ec }
}
)
);
} else if (options.webpackIgnore) {
return newline;
}
}
2023-05-19 19:44:13 +08:00
if (url.length === 0) {
2024-10-16 03:32:17 +08:00
const { line: sl, column: sc } = locConverter.get(start);
const { line: el, column: ec } = locConverter.get(newline);
const dep = new ConstDependency("", [start, newline]);
2023-05-19 19:44:13 +08:00
module.addPresentationalDependency(dep);
dep.setLoc(sl, sc, el, ec);
2024-10-16 03:32:17 +08:00
return newline;
2023-05-19 19:44:13 +08:00
}
2024-10-16 03:32:17 +08:00
let layer;
2023-05-19 19:44:13 +08:00
2024-10-16 03:32:17 +08:00
if (tokens[1]) {
layer = input.slice(tokens[1][0] + 6, tokens[1][1] - 1).trim();
}
let supports;
if (tokens[2]) {
supports = input.slice(tokens[2][0] + 9, tokens[2][1] - 1).trim();
}
const last = tokens[2] || tokens[1] || tokens[0];
const mediaStart = walkCssTokens.eatWhitespaceAndComments(
input,
last[1]
);
let media;
if (mediaStart !== semi - 1) {
media = input.slice(mediaStart, semi - 1).trim();
}
const { line: sl, column: sc } = locConverter.get(start);
const { line: el, column: ec } = locConverter.get(newline);
const dep = new CssImportDependency(
url,
[start, newline],
layer,
supports && supports.length > 0 ? supports : undefined,
media && media.length > 0 ? media : undefined
);
dep.setLoc(sl, sc, el, ec);
module.addDependency(dep);
return newline;
2021-12-01 20:27:00 +08:00
}
2024-10-16 03:32:17 +08:00
default: {
if (isModules) {
if (OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE.test(name)) {
const ident = walkCssTokens.eatIdentSequenceOrString(
input,
end
);
if (!ident) return end;
const name =
ident[2] === true
? input.slice(ident[0], ident[1])
: input.slice(ident[0] + 1, ident[1] - 1);
if (isLocalMode()) {
const { line: sl, column: sc } = locConverter.get(ident[0]);
const { line: el, column: ec } = locConverter.get(ident[1]);
const dep = new CssLocalIdentifierDependency(name, [
ident[0],
ident[1]
]);
dep.setLoc(sl, sc, el, ec);
module.addDependency(dep);
}
return ident[1];
} else if (name === "@property") {
2024-10-16 11:04:30 +08:00
const ident = walkCssTokens.eatIdentSequence(input, end);
2024-10-16 03:32:17 +08:00
if (!ident) return end;
let name = input.slice(ident[0], ident[1]);
if (!name.startsWith("--")) return end;
name = name.slice(2);
declaredCssVariables.add(name);
if (isLocalMode()) {
const { line: sl, column: sc } = locConverter.get(ident[0]);
const { line: el, column: ec } = locConverter.get(ident[1]);
const dep = new CssLocalIdentifierDependency(
name,
[ident[0], ident[1]],
"--"
);
dep.setLoc(sl, sc, el, ec);
module.addDependency(dep);
}
return ident[1];
} else if (isModules && name === "@scope") {
modeData = isLocalMode() ? "local" : "global";
isNextRulePrelude = true;
return end;
}
2023-05-20 00:05:14 +08:00
2024-10-16 03:32:17 +08:00
isNextRulePrelude = false;
}
2021-12-01 20:27:00 +08:00
}
2024-10-16 03:32:17 +08:00
}
return end;
},
semicolon: (input, start, end) => {
2024-10-16 11:04:30 +08:00
if (isModules && scope === CSS_MODE_IN_BLOCK) {
if (isLocalMode()) {
processDeclarationValueDone(input);
inAnimationProperty = false;
}
2024-10-16 11:04:30 +08:00
isNextRulePrelude = isNextNestedSyntax(input, end);
2021-12-01 20:27:00 +08:00
}
2021-11-30 19:55:51 +08:00
return end;
2021-12-14 23:02:26 +08:00
},
identifier: (input, start, end) => {
2023-05-05 07:53:57 +08:00
switch (scope) {
2023-05-05 07:49:09 +08:00
case CSS_MODE_IN_BLOCK: {
if (isLocalMode()) {
// Handle only top level values and not inside functions
if (inAnimationProperty && balanced.length === 0) {
2024-10-16 03:32:17 +08:00
lastIdentifier = [start, end, true];
2023-05-05 07:49:09 +08:00
} else {
return processLocalDeclaration(input, start, end);
}
2021-12-14 23:02:26 +08:00
}
break;
2023-05-05 03:24:13 +08:00
}
2021-12-14 23:02:26 +08:00
}
return end;
},
2024-10-09 17:09:36 +08:00
delim: (input, start, end) => {
if (isNextRulePrelude && isLocalMode()) {
2024-10-16 03:32:17 +08:00
const ident = walkCssTokens.skipCommentsAndEatIdentSequence(
input,
end
);
if (!ident) return end;
const name = input.slice(ident[0], ident[1]);
const dep = new CssLocalIdentifierDependency(name, [
ident[0],
ident[1]
]);
const { line: sl, column: sc } = locConverter.get(ident[0]);
const { line: el, column: ec } = locConverter.get(ident[1]);
dep.setLoc(sl, sc, el, ec);
module.addDependency(dep);
return ident[1];
2021-12-15 15:34:31 +08:00
}
2023-05-05 06:52:08 +08:00
2021-12-15 15:34:31 +08:00
return end;
},
2024-10-09 14:21:00 +08:00
hash: (input, start, end, isID) => {
2024-10-09 17:09:36 +08:00
if (isNextRulePrelude && isLocalMode() && isID) {
2024-10-16 11:04:30 +08:00
const valueStart = start + 1;
const name = input.slice(valueStart, end);
const dep = new CssLocalIdentifierDependency(name, [valueStart, end]);
2023-05-05 06:52:08 +08:00
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);
2023-05-05 02:26:32 +08:00
}
2024-10-09 17:09:36 +08:00
return end;
},
colon: (input, start, end) => {
if (isModules) {
2024-10-16 03:32:17 +08:00
const ident = walkCssTokens.skipCommentsAndEatIdentSequence(
input,
end
);
if (!ident) return end;
2024-10-09 18:20:41 +08:00
const name = input.slice(ident[0], ident[1]).toLowerCase();
2024-10-09 17:09:36 +08:00
switch (scope) {
case CSS_MODE_TOP_LEVEL: {
2024-10-09 18:20:41 +08:00
if (name === "export") {
const pos = parseExports(input, ident[1]);
2024-10-09 17:09:36 +08:00
const dep = new ConstDependency("", [start, pos]);
module.addPresentationalDependency(dep);
return pos;
}
}
// falls through
default: {
2024-10-09 18:20:41 +08:00
if (isNextRulePrelude) {
const isFn = input.charCodeAt(ident[1]) === CC_LEFT_PARENTHESIS;
if (isFn && name === "local") {
const end = ident[1] + 1;
modeData = "local";
2024-10-09 17:09:36 +08:00
const dep = new ConstDependency("", [start, end]);
module.addPresentationalDependency(dep);
2024-10-09 18:20:41 +08:00
balanced.push([":local", start, end]);
2024-10-09 17:09:36 +08:00
return end;
2024-10-09 18:20:41 +08:00
} else if (name === "local") {
2024-10-09 17:09:36 +08:00
modeData = "local";
// Eat extra whitespace
2024-10-09 18:20:41 +08:00
end = walkCssTokens.eatWhitespace(input, ident[1]);
if (ident[1] === end) {
this._emitWarning(
state,
`Missing whitespace after ':local' in '${input.slice(
start,
2024-10-16 03:32:17 +08:00
eatUntilLeftCurly(input, end) + 1
)}'`,
locConverter,
start,
end
);
}
2024-10-09 18:20:41 +08:00
const dep = new ConstDependency("", [start, end]);
module.addPresentationalDependency(dep);
return end;
} else if (isFn && name === "global") {
const end = ident[1] + 1;
modeData = "global";
const dep = new ConstDependency("", [start, end]);
module.addPresentationalDependency(dep);
balanced.push([":global", start, end]);
return end;
} else if (name === "global") {
modeData = "global";
// Eat extra whitespace
2024-10-09 18:20:41 +08:00
end = walkCssTokens.eatWhitespace(input, ident[1]);
if (ident[1] === end) {
this._emitWarning(
state,
`Missing whitespace after ':global' in '${input.slice(
start,
2024-10-16 03:32:17 +08:00
eatUntilLeftCurly(input, end) + 1
)}'`,
locConverter,
start,
end
);
}
2024-10-09 17:09:36 +08:00
const dep = new ConstDependency("", [start, end]);
module.addPresentationalDependency(dep);
return end;
}
}
}
}
}
2023-05-05 02:26:32 +08:00
return end;
},
2023-04-25 09:17:51 +08:00
function: (input, start, end) => {
2024-10-16 03:32:17 +08:00
const name = input
.slice(start, end - 1)
.replace(/\\/g, "")
.toLowerCase();
2023-04-25 09:17:51 +08:00
2024-10-09 18:20:41 +08:00
balanced.push([name, start, end]);
2024-10-16 03:32:17 +08:00
switch (name) {
case "src":
case "url": {
const string = walkCssTokens.eatString(input, end);
if (!string) return end;
2024-10-16 22:42:26 +08:00
const { options, errors: commentErrors } = this.parseCommentOptions(
[start, end]
);
if (commentErrors) {
for (const e of commentErrors) {
const { comment } = e;
state.module.addWarning(
new CommentCompilationWarning(
`Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
comment.loc
)
);
}
}
if (options && options.webpackIgnore !== undefined) {
if (typeof options.webpackIgnore !== "boolean") {
const { line: sl, column: sc } = locConverter.get(string[0]);
const { line: el, column: ec } = locConverter.get(string[1]);
state.module.addWarning(
new UnsupportedFeatureWarning(
`\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
{
start: { line: sl, column: sc },
end: { line: el, column: ec }
}
)
);
} else if (options.webpackIgnore) {
return end;
}
}
2024-10-16 03:32:17 +08:00
const value = normalizeUrl(
input.slice(string[0] + 1, string[1] - 1),
true
2023-05-05 04:01:16 +08:00
);
2024-10-16 03:32:17 +08:00
// Ignore `url()`, `url('')` and `url("")`, they are valid by spec
if (value.length === 0) return end;
const isUrl = name === "url" || name === "src";
const dep = new CssUrlDependency(
value,
[string[0], string[1]],
isUrl ? "string" : "url"
);
const { line: sl, column: sc } = locConverter.get(string[0]);
const { line: el, column: ec } = locConverter.get(string[1]);
2023-05-05 04:01:16 +08:00
dep.setLoc(sl, sc, el, ec);
module.addDependency(dep);
2024-10-16 03:32:17 +08:00
module.addCodeGenerationDependency(dep);
return string[1];
}
default: {
if (IMAGE_SET_FUNCTION.test(name)) {
const values = walkCssTokens.eatImageSetStrings(input, end);
if (values.length === 0) return end;
for (const string of values) {
const value = normalizeUrl(
input.slice(string[0] + 1, string[1] - 1),
true
);
if (value.length === 0) return end;
const dep = new CssUrlDependency(
value,
[string[0], string[1]],
"url"
);
const { line: sl, column: sc } = locConverter.get(string[0]);
const { line: el, column: ec } = locConverter.get(string[1]);
dep.setLoc(sl, sc, el, ec);
module.addDependency(dep);
module.addCodeGenerationDependency(dep);
}
// Can contain `url()` inside, so let's return end to allow parse them
return end;
} else if (isLocalMode()) {
// Don't rename animation name when we have `var()` function
if (inAnimationProperty && balanced.length === 1) {
lastIdentifier = undefined;
}
if (name === "var") {
const ident = walkCssTokens.eatIdentSequence(input, end);
if (!ident) return end;
const name = input.slice(ident[0], ident[1]);
if (!name.startsWith("--")) return end;
const { line: sl, column: sc } = locConverter.get(ident[0]);
const { line: el, column: ec } = locConverter.get(ident[1]);
const dep = new CssSelfLocalIdentifierDependency(
name.slice(2),
[ident[0], ident[1]],
"--",
declaredCssVariables
);
dep.setLoc(sl, sc, el, ec);
module.addDependency(dep);
return ident[1];
}
}
2023-04-25 09:17:51 +08:00
}
}
2023-05-05 04:01:16 +08:00
2023-04-25 09:17:51 +08:00
return end;
},
2021-12-14 23:02:26 +08:00
leftParenthesis: (input, start, end) => {
2023-04-26 08:45:15 +08:00
balanced.push(["(", start, end]);
2021-12-14 23:02:26 +08:00
return end;
},
rightParenthesis: (input, start, end) => {
2023-05-05 00:47:38 +08:00
const popped = balanced.pop();
2023-04-14 06:52:50 +08:00
2023-05-05 06:52:08 +08:00
if (
isModules &&
2023-05-05 07:20:26 +08:00
popped &&
2023-05-05 06:52:08 +08:00
(popped[0] === ":local" || popped[0] === ":global")
) {
modeData = balanced[balanced.length - 1]
? /** @type {"local" | "global"} */
2024-07-31 05:43:19 +08:00
(balanced[balanced.length - 1][0])
2023-05-05 06:52:08 +08:00
: undefined;
const dep = new ConstDependency("", [start, end]);
module.addPresentationalDependency(dep);
}
2023-04-14 04:25:46 +08:00
2021-12-14 23:02:26 +08:00
return end;
},
comma: (input, start, end) => {
if (isModules) {
2023-05-05 10:13:13 +08:00
// Reset stack for `:global .class :local .class-other` selector after
modeData = undefined;
2024-10-16 11:04:30 +08:00
if (scope === CSS_MODE_IN_BLOCK && isLocalMode()) {
processDeclarationValueDone(input);
2023-05-05 00:47:38 +08:00
}
2021-12-14 23:02:26 +08:00
}
return end;
2021-11-30 19:55:51 +08:00
}
});
2024-10-01 03:05:27 +08:00
/** @type {BuildInfo} */
(module.buildInfo).strict = true;
/** @type {BuildMeta} */
(module.buildMeta).exportsType = this.namedExports
? "namespace"
: "default";
if (!this.namedExports) {
2024-10-01 03:05:27 +08:00
/** @type {BuildMeta} */
(module.buildMeta).defaultObject = "redirect";
}
2021-12-01 16:50:13 +08:00
module.addDependency(new StaticExportsDependency([], true));
2021-11-30 19:55:51 +08:00
return state;
}
2024-10-16 22:42:26 +08:00
/**
* @param {Range} range range
* @returns {Comment[]} comments in the range
*/
getComments(range) {
if (!this.comments) return [];
const [rangeStart, rangeEnd] = range;
/**
* @param {Comment} comment comment
* @param {number} needle needle
* @returns {number} compared
*/
const compare = (comment, needle) =>
/** @type {Range} */ (comment.range)[0] - needle;
const comments = /** @type {Comment[]} */ (this.comments);
let idx = binarySearchBounds.ge(comments, rangeStart, compare);
/** @type {Comment[]} */
const commentsInRange = [];
while (
comments[idx] &&
/** @type {Range} */ (comments[idx].range)[1] <= rangeEnd
) {
commentsInRange.push(comments[idx]);
idx++;
}
return commentsInRange;
}
/**
* @param {Range} range range of the comment
* @returns {{ options: Record<string, any> | null, errors: (Error & { comment: Comment })[] | null }} result
*/
parseCommentOptions(range) {
const comments = this.getComments(range);
if (comments.length === 0) {
return EMPTY_COMMENT_OPTIONS;
}
/** @type {Record<string, any> } */
const options = {};
/** @type {(Error & { comment: Comment })[]} */
const errors = [];
for (const comment of comments) {
const { value } = comment;
if (value && webpackCommentRegExp.test(value)) {
// try compile only if webpack options comment is present
try {
for (let [key, val] of Object.entries(
vm.runInContext(
`(function(){return {${value}};})()`,
this.magicCommentContext
)
)) {
if (typeof val === "object" && val !== null) {
val =
val.constructor.name === "RegExp"
? new RegExp(val)
: JSON.parse(JSON.stringify(val));
}
options[key] = val;
}
} catch (err) {
const newErr = new Error(String(/** @type {Error} */ (err).message));
newErr.stack = String(/** @type {Error} */ (err).stack);
Object.assign(newErr, { comment });
errors.push(/** @type (Error & { comment: Comment }) */ (newErr));
}
}
}
return { options, errors };
}
2021-11-30 19:55:51 +08:00
}
module.exports = CssParser;