webpack/lib/css/CssParser.js

907 lines
26 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";
2023-05-02 18:12:41 +08:00
const ModuleDependencyWarning = require("../ModuleDependencyWarning");
2021-11-30 19:55:51 +08:00
const Parser = require("../Parser");
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");
const walkCssTokens = require("./walkCssTokens");
/** @typedef {import("../Parser").ParserState} ParserState */
/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
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);
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;
2023-04-15 00:19:27 +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;
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) {
return String.fromCharCode(parseInt(match.slice(1).trim(), 16));
} else {
return match[1];
}
});
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);
} catch (error) {
// 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;
}
}
2021-12-01 20:27:00 +08:00
const CSS_MODE_TOP_LEVEL = 0;
const CSS_MODE_IN_RULE = 1;
const CSS_MODE_IN_LOCAL_RULE = 2;
const CSS_MODE_AT_IMPORT_EXPECT_URL = 3;
2023-04-25 09:17:51 +08:00
const CSS_MODE_AT_IMPORT_EXPECT_LAYER_OR_SUPPORTS_OR_MEDIA = 4;
2023-05-02 08:24:36 +08:00
const CSS_MODE_AT_IMPORT_INVALID = 5;
2023-05-02 09:23:46 +08:00
const CSS_MODE_AT_NAMESPACE_INVALID = 6;
2021-12-01 20:27:00 +08:00
2021-11-30 19:55:51 +08:00
class CssParser extends Parser {
2023-05-05 00:47:38 +08:00
constructor({ allowModeSwitch = true, defaultMode = "global" } = {}) {
2021-11-30 19:55:51 +08:00
super();
2021-12-15 15:34:31 +08:00
this.allowModeSwitch = allowModeSwitch;
this.defaultMode = defaultMode;
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");
}
if (source[0] === "\ufeff") {
source = source.slice(1);
}
const module = state.module;
2021-12-01 21:15:19 +08:00
const locConverter = new LocConverter(source);
2023-05-05 05:38:41 +08:00
/** @type {Set<string>}*/
const declaredCssVariables = new Set();
2023-04-29 02:50:41 +08:00
/** @type {number} */
2021-12-01 20:27:00 +08:00
let mode = CSS_MODE_TOP_LEVEL;
2023-04-29 02:50:41 +08:00
/** @type {number} */
2021-12-14 23:02:26 +08:00
let modeNestingLevel = 0;
2023-05-02 08:24:36 +08:00
/** @type {boolean} */
let allowImportAtRule = true;
2023-05-05 05:27:59 +08:00
/** @type {"local" | "global" | undefined} */
2021-12-01 20:27:00 +08:00
let modeData = undefined;
2023-04-29 02:50:41 +08:00
/** @type {[number, number] | undefined} */
let lastIdentifier = undefined;
2023-04-14 06:52:50 +08:00
/** @type [string, number, number][] */
2023-04-26 08:45:15 +08:00
let balanced = [];
2023-05-05 05:27:59 +08:00
/** @type {undefined | { start: number, end: number, url?: string, media?: string, supports?: string, layer?: string }} */
let importData = undefined;
2023-05-05 04:59:30 +08:00
/** @type {boolean} */
let inAnimationProperty = false;
2023-05-05 06:13:27 +08:00
/** @type {boolean} */
let isNestedSyntax = false;
2023-04-14 04:25:46 +08:00
2023-05-05 02:28:09 +08:00
/**
* @returns {boolean} true, when in local mode
*/
const isLocalMode = () =>
modeData === "local" ||
(this.defaultMode === "local" && modeData === undefined);
2023-05-05 05:38:41 +08:00
/**
* @param {string} chars characters
* @returns {(input: string, pos: number) => number} function to eat characters
*/
2021-12-14 23:02:26 +08:00
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
);
charCodes.forEach(cc => (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;
}
};
};
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
};
const eatExportName = eatUntil(":};/");
const eatExportValue = 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;
let start = pos;
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;
};
const eatPropertyName = 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 (
2023-05-05 03:24:13 +08:00
!propertyName.startsWith("--") &&
2023-04-17 00:43:06 +08:00
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]);
const name = input.slice(lastIdentifier[0], lastIdentifier[1]);
const dep = new CssSelfLocalIdentifierDependency(name, lastIdentifier);
dep.setLoc(sl, sc, el, ec);
module.addDependency(dep);
2023-05-05 04:59:30 +08:00
lastIdentifier = undefined;
}
};
const eatAtRuleNested = eatUntil("{};/");
const eatKeyframes = eatUntil("{};/");
const eatNameInVar = eatUntil(",)};/");
2021-11-30 19:55:51 +08:00
walkCssTokens(source, {
isSelector: () => {
2023-05-05 06:13:27 +08:00
return mode === CSS_MODE_TOP_LEVEL || isNestedSyntax;
},
2023-05-02 08:24:36 +08:00
url: (input, start, end, contentStart, contentEnd) => {
let value = normalizeUrl(input.slice(contentStart, contentEnd), false);
2021-12-01 20:27:00 +08:00
switch (mode) {
case CSS_MODE_AT_IMPORT_EXPECT_URL: {
2023-05-05 05:27:59 +08:00
importData.url = value;
importData.end = end;
2023-04-25 09:17:51 +08:00
mode = CSS_MODE_AT_IMPORT_EXPECT_LAYER_OR_SUPPORTS_OR_MEDIA;
break;
}
// Do not parse URLs in `supports(...)`
case CSS_MODE_AT_IMPORT_EXPECT_LAYER_OR_SUPPORTS_OR_MEDIA: {
2021-12-01 20:27:00 +08:00
break;
}
2023-05-02 08:24:36 +08:00
// Do not parse URLs in import between rules
2023-05-02 09:23:46 +08:00
case CSS_MODE_AT_NAMESPACE_INVALID:
2023-05-02 08:24:36 +08:00
case CSS_MODE_AT_IMPORT_INVALID: {
break;
}
2021-12-01 20:27:00 +08:00
default: {
2023-05-02 06:24:01 +08:00
// Ignore `url()`, `url('')` and `url("")`, they are valid by spec
if (value.length === 0) {
break;
}
2021-12-03 23:23:09 +08:00
const dep = new CssUrlDependency(value, [start, end], "url");
2021-12-01 21:15:19 +08:00
const { line: sl, column: sc } = locConverter.get(start);
const { line: el, column: ec } = locConverter.get(end);
dep.setLoc(sl, sc, el, ec);
2021-12-01 20:27:00 +08:00
module.addDependency(dep);
module.addCodeGenerationDependency(dep);
break;
}
}
return end;
},
string: (input, start, end) => {
switch (mode) {
case CSS_MODE_AT_IMPORT_EXPECT_URL: {
2023-05-05 05:27:59 +08:00
importData.url = normalizeUrl(
input.slice(start + 1, end - 1),
true
);
importData.end = end;
2023-04-25 09:17:51 +08:00
const insideURLFunction =
2023-04-26 08:45:15 +08:00
balanced[balanced.length - 1] &&
balanced[balanced.length - 1][0] === "url";
2023-04-25 09:17:51 +08:00
if (!insideURLFunction) {
mode = CSS_MODE_AT_IMPORT_EXPECT_LAYER_OR_SUPPORTS_OR_MEDIA;
}
break;
}
// Do not parse URLs in `supports(...)`
case CSS_MODE_AT_IMPORT_EXPECT_LAYER_OR_SUPPORTS_OR_MEDIA: {
2021-12-01 20:27:00 +08:00
break;
}
2023-04-14 06:52:50 +08:00
default: {
2023-04-14 07:35:53 +08:00
// TODO move escaped parsing to tokenizer
2023-04-26 08:52:17 +08:00
const last = balanced[balanced.length - 1];
2023-04-14 06:52:50 +08:00
2023-04-14 04:25:46 +08:00
if (
2023-04-26 08:52:17 +08:00
last &&
(last[0].replace(/\\/g, "").toLowerCase() === "url" ||
IMAGE_SET_FUNCTION.test(last[0].replace(/\\/g, "")))
2023-04-14 04:25:46 +08:00
) {
let value = normalizeUrl(input.slice(start + 1, end - 1), true);
2023-05-02 06:24:01 +08:00
// Ignore `url()`, `url('')` and `url("")`, they are valid by spec
if (value.length === 0) {
2023-04-14 04:25:46 +08:00
break;
}
2023-04-26 08:52:17 +08:00
const isUrl = last[0].replace(/\\/g, "").toLowerCase() === "url";
2023-04-14 06:52:50 +08:00
const dep = new CssUrlDependency(
value,
[start, end],
isUrl ? "string" : "url"
);
2023-04-14 04:25:46 +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);
module.addCodeGenerationDependency(dep);
}
}
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();
2021-12-01 20:27:00 +08:00
if (name === "@namespace") {
2023-05-02 09:23:46 +08:00
mode = CSS_MODE_AT_NAMESPACE_INVALID;
2023-05-02 08:24:36 +08:00
this._emitWarning(
state,
"@namespace is not supported in bundled CSS",
locConverter,
start,
end
);
return end;
} else if (name === "@import") {
if (!allowImportAtRule) {
mode = CSS_MODE_AT_IMPORT_INVALID;
this._emitWarning(
state,
"Any @import rules must precede all other rules",
locConverter,
start,
end
2021-12-17 04:07:22 +08:00
);
2023-05-02 08:24:36 +08:00
return end;
2021-12-17 04:07:22 +08:00
}
2023-05-02 08:24:36 +08:00
2021-12-01 20:27:00 +08:00
mode = CSS_MODE_AT_IMPORT_EXPECT_URL;
2023-05-05 05:27:59 +08:00
importData = { start, end };
} else if (
2023-05-05 02:28:09 +08:00
isLocalMode() &&
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;
}
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;
2023-05-05 07:20:26 +08:00
modeData = "local";
modeNestingLevel = 1;
return pos + 1;
2023-05-05 02:28:09 +08:00
} else if (isLocalMode() && name === "@property") {
2023-05-04 19:50:11 +08:00
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 { line: sl, column: sc } = locConverter.get(pos);
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);
pos = propertyNameEnd;
2023-05-05 07:20:26 +08:00
modeData = "local";
2023-05-04 19:50:11 +08:00
modeNestingLevel = 1;
return pos + 1;
2023-05-04 19:38:10 +08:00
} else if (
name === "@media" ||
name === "@supports" ||
name === "@layer" ||
name === "@container"
2023-05-04 19:38:10 +08:00
) {
2023-05-05 07:20:26 +08:00
modeData = "local";
// TODO handle nested CSS syntax
let pos = end;
const [newPos] = eatText(input, pos, eatAtRuleNested);
pos = newPos;
if (pos === input.length) return pos;
if (input.charCodeAt(pos) !== CC_LEFT_CURLY) {
2023-05-02 17:15:52 +08:00
this._emitWarning(
state,
2023-05-05 01:50:29 +08:00
`Unexpected ${input[pos]} at ${pos} during parsing of @media, @supports, @layer or @container (expected '{')`,
2023-05-02 17:15:52 +08:00
locConverter,
start,
pos
);
2023-05-02 17:15:52 +08:00
return pos;
}
return pos + 1;
} else {
mode = CSS_MODE_IN_RULE;
}
2021-12-01 20:27:00 +08:00
return end;
},
semicolon: (input, start, end) => {
switch (mode) {
2023-05-02 17:15:52 +08:00
case CSS_MODE_AT_IMPORT_EXPECT_URL: {
this._emitWarning(
state,
`Expected URL for @import at ${start}`,
locConverter,
start,
end
);
return end;
}
2023-04-25 09:17:51 +08:00
case CSS_MODE_AT_IMPORT_EXPECT_LAYER_OR_SUPPORTS_OR_MEDIA: {
2023-05-05 05:27:59 +08:00
if (!importData.url === undefined) {
2023-05-02 17:15:52 +08:00
this._emitWarning(
state,
2023-05-05 05:27:59 +08:00
`Expected URL for @import at ${importData.start}`,
2023-05-02 17:15:52 +08:00
locConverter,
2023-05-05 05:27:59 +08:00
importData.start,
importData.end
2023-04-26 04:31:35 +08:00
);
2023-05-02 17:15:52 +08:00
return end;
2023-04-26 04:31:35 +08:00
}
2023-04-25 09:17:51 +08:00
const semicolonPos = end;
2023-05-01 22:46:47 +08:00
end = walkCssTokens.eatWhiteLine(input, end + 1);
2023-05-05 05:27:59 +08:00
const { line: sl, column: sc } = locConverter.get(importData.start);
2021-12-03 02:29:55 +08:00
const { line: el, column: ec } = locConverter.get(end);
2023-04-26 08:07:13 +08:00
const pos = walkCssTokens.eatWhitespaceAndComments(
input,
2023-05-05 05:27:59 +08:00
importData.end
2023-04-26 08:07:13 +08:00
);
// Prevent to consider comments as a part of media query
if (pos !== semicolonPos - 1) {
2023-05-05 05:27:59 +08:00
importData.media = input
.slice(importData.end, semicolonPos - 1)
2023-04-26 08:07:13 +08:00
.trim();
}
2021-12-01 20:27:00 +08:00
const dep = new CssImportDependency(
2023-05-05 05:27:59 +08:00
importData.url.trim(),
[importData.start, end],
importData.layer,
importData.supports,
importData.media && importData.media.length > 0
? importData.media
2023-04-26 08:07:13 +08:00
: undefined
2021-12-01 20:27:00 +08:00
);
2021-12-01 21:15:19 +08:00
dep.setLoc(sl, sc, el, ec);
2021-12-01 20:27:00 +08:00
module.addDependency(dep);
2023-05-05 05:27:59 +08:00
importData = undefined;
2023-04-25 09:17:51 +08:00
mode = CSS_MODE_TOP_LEVEL;
2021-12-01 20:27:00 +08:00
break;
}
case CSS_MODE_IN_LOCAL_RULE: {
2023-05-05 04:59:30 +08:00
processDeclarationValueDone(input);
2023-05-05 06:13:27 +08:00
// Handle nested syntax
let pos = walkCssTokens.eatWhitespaceAndComments(input, end);
// Nested block
if (input[pos] !== "}") {
// According spec only identifier can be used as a property name
const isIdentifier = walkCssTokens.isIdentStartCodePoint(
input.charCodeAt(pos)
);
if (!isIdentifier) {
isNestedSyntax = true;
}
}
2023-05-05 04:59:30 +08:00
inAnimationProperty = false;
2023-05-05 03:24:13 +08:00
return 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
},
leftCurlyBracket: (input, start, end) => {
switch (mode) {
case CSS_MODE_TOP_LEVEL:
2023-05-02 08:24:36 +08:00
allowImportAtRule = false;
2023-05-05 02:28:09 +08:00
mode = isLocalMode() ? CSS_MODE_IN_LOCAL_RULE : CSS_MODE_IN_RULE;
2021-12-14 23:02:26 +08:00
modeNestingLevel = 1;
break;
case CSS_MODE_IN_RULE:
case CSS_MODE_IN_LOCAL_RULE:
2021-12-14 23:02:26 +08:00
modeNestingLevel++;
break;
}
return end;
},
rightCurlyBracket: (input, start, end) => {
switch (mode) {
case CSS_MODE_IN_LOCAL_RULE:
2023-05-05 04:59:30 +08:00
processDeclarationValueDone(input);
inAnimationProperty = false;
/* falls through */
2021-12-14 23:02:26 +08:00
case CSS_MODE_IN_RULE:
if (--modeNestingLevel === 0) {
mode = CSS_MODE_TOP_LEVEL;
2023-05-05 06:13:27 +08:00
isNestedSyntax = false;
modeData = undefined;
}
break;
}
return end;
},
identifier: (input, start, end) => {
switch (mode) {
2023-05-05 03:24:13 +08:00
case CSS_MODE_IN_LOCAL_RULE: {
2023-05-05 04:41:12 +08:00
// Handle only top level values and not inside functions
2023-05-05 04:59:30 +08:00
if (inAnimationProperty && balanced.length === 0) {
lastIdentifier = [start, end];
2023-05-05 02:56:12 +08:00
} else {
2023-05-05 03:40:00 +08:00
return processLocalDeclaration(input, start, end);
2021-12-14 23:02:26 +08:00
}
break;
2023-05-05 03:24:13 +08:00
}
2023-04-26 05:23:21 +08:00
case CSS_MODE_AT_IMPORT_EXPECT_LAYER_OR_SUPPORTS_OR_MEDIA: {
2023-04-26 06:00:39 +08:00
if (input.slice(start, end).toLowerCase() === "layer") {
2023-05-05 05:27:59 +08:00
importData.layer = "";
importData.end = end;
2023-04-26 05:23:21 +08:00
}
break;
}
2021-12-14 23:02:26 +08:00
}
return end;
},
2021-12-15 15:34:31 +08:00
class: (input, start, end) => {
2023-05-05 06:52:08 +08:00
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);
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;
},
2023-05-05 02:26:32 +08:00
id: (input, start, end) => {
2023-05-05 06:52:08 +08:00
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);
2023-05-05 02:26:32 +08:00
}
return end;
},
2023-04-25 09:17:51 +08:00
function: (input, start, end) => {
let name = input.slice(start, end - 1);
2023-04-26 08:45:15 +08:00
balanced.push([name, start, end]);
2023-04-25 09:17:51 +08:00
2023-05-05 04:01:16 +08:00
if (isLocalMode()) {
name = name.toLowerCase();
2023-05-05 04:59:30 +08:00
// Don't rename animation name when we have `var()` function
if (inAnimationProperty && balanced.length === 1) {
lastIdentifier = undefined;
}
2023-05-05 04:01:16 +08:00
if (name === "var") {
let 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;
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-04-26 08:52:17 +08:00
const last = balanced[balanced.length - 1];
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 (
this.allowModeSwitch &&
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"} */
(balanced[balanced.length - 1][0])
: undefined;
const dep = new ConstDependency("", [start, end]);
module.addPresentationalDependency(dep);
return end;
}
switch (mode) {
2023-04-25 09:17:51 +08:00
case CSS_MODE_AT_IMPORT_EXPECT_URL: {
2023-04-26 08:52:17 +08:00
if (last && last[0] === "url") {
2023-05-05 05:27:59 +08:00
importData.end = end;
2023-04-25 09:17:51 +08:00
mode = CSS_MODE_AT_IMPORT_EXPECT_LAYER_OR_SUPPORTS_OR_MEDIA;
}
break;
}
case CSS_MODE_AT_IMPORT_EXPECT_LAYER_OR_SUPPORTS_OR_MEDIA: {
2023-04-26 08:52:17 +08:00
if (last && last[0].toLowerCase() === "layer") {
2023-05-05 05:27:59 +08:00
importData.layer = input.slice(last[2], end - 1).trim();
importData.end = end;
2023-04-26 08:52:17 +08:00
} else if (last && last[0].toLowerCase() === "supports") {
2023-05-05 05:27:59 +08:00
importData.supports = input.slice(last[2], end - 1).trim();
importData.end = end;
2023-04-25 09:17:51 +08:00
}
break;
}
}
2023-04-14 04:25:46 +08:00
2021-12-14 23:02:26 +08:00
return end;
},
pseudoClass: (input, start, end) => {
2023-05-04 23:21:57 +08:00
if (this.allowModeSwitch) {
2023-05-05 06:52:08 +08:00
const name = input.slice(start, end).toLowerCase();
if (name === ":global") {
modeData = "global";
2023-05-05 07:05:37 +08:00
// Eat extra whitespace and comments
end = walkCssTokens.eatWhitespace(input, end);
2023-05-05 06:52:08 +08:00
const dep = new ConstDependency("", [start, end]);
module.addPresentationalDependency(dep);
2023-05-05 07:05:37 +08:00
return end;
2023-05-05 06:52:08 +08:00
} else if (name === ":local") {
modeData = "local";
2023-05-05 07:05:37 +08:00
// Eat extra whitespace and comments
end = walkCssTokens.eatWhitespace(input, end);
2023-05-05 06:52:08 +08:00
const dep = new ConstDependency("", [start, end]);
module.addPresentationalDependency(dep);
2023-05-05 07:05:37 +08:00
return end;
2023-05-05 06:52:08 +08:00
}
2023-05-04 23:21:57 +08:00
switch (mode) {
case CSS_MODE_TOP_LEVEL: {
2023-05-05 06:52:08 +08:00
if (name === ":export") {
2023-05-04 23:21:57 +08:00
const pos = parseExports(input, end);
const dep = new ConstDependency("", [start, pos]);
module.addPresentationalDependency(dep);
return pos;
}
break;
2021-12-14 23:02:26 +08:00
}
}
}
2023-05-05 06:52:08 +08:00
2021-12-14 23:02:26 +08:00
return end;
},
2021-12-15 15:34:31 +08:00
pseudoFunction: (input, start, end) => {
2023-04-14 06:52:50 +08:00
let name = input.slice(start, end - 1);
2023-04-26 08:45:15 +08:00
balanced.push([name, start, end]);
2023-04-14 06:52:50 +08:00
2023-05-05 00:18:13 +08:00
if (this.allowModeSwitch) {
2023-05-05 06:52:08 +08:00
name = name.toLowerCase();
2023-04-14 06:52:50 +08:00
2023-05-05 06:52:08 +08:00
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);
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;
},
2021-12-14 23:02:26 +08:00
comma: (input, start, end) => {
2023-05-05 00:47:38 +08:00
if (this.allowModeSwitch) {
2023-05-05 06:52:08 +08:00
// Reset stack for `:global .class :local .class-other` selector after `,`
modeData = undefined;
2023-05-05 00:47:38 +08:00
switch (mode) {
case CSS_MODE_IN_LOCAL_RULE:
2023-05-05 04:59:30 +08:00
processDeclarationValueDone(input);
2023-05-05 00:47:38 +08:00
break;
}
2021-12-14 23:02:26 +08:00
}
return end;
2021-11-30 19:55:51 +08:00
}
});
module.buildInfo.strict = true;
2021-12-01 16:50:13 +08:00
module.buildMeta.exportsType = "namespace";
module.addDependency(new StaticExportsDependency([], true));
2021-11-30 19:55:51 +08:00
return state;
}
}
module.exports = CssParser;