summaryrefslogtreecommitdiff
path: root/node_modules/postcss-normalize-string/src/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/postcss-normalize-string/src/index.js')
-rw-r--r--node_modules/postcss-normalize-string/src/index.js319
1 files changed, 319 insertions, 0 deletions
diff --git a/node_modules/postcss-normalize-string/src/index.js b/node_modules/postcss-normalize-string/src/index.js
new file mode 100644
index 00000000..fa74851c
--- /dev/null
+++ b/node_modules/postcss-normalize-string/src/index.js
@@ -0,0 +1,319 @@
+'use strict';
+const valueParser = require('postcss-value-parser');
+
+/*
+ * Constants (parser usage)
+ */
+
+const SINGLE_QUOTE = "'".charCodeAt(0);
+const DOUBLE_QUOTE = '"'.charCodeAt(0);
+const BACKSLASH = '\\'.charCodeAt(0);
+const NEWLINE = '\n'.charCodeAt(0);
+const SPACE = ' '.charCodeAt(0);
+const FEED = '\f'.charCodeAt(0);
+const TAB = '\t'.charCodeAt(0);
+const CR = '\r'.charCodeAt(0);
+
+const WORD_END = /[ \n\t\r\f'"\\]/g;
+
+/*
+ * Constants (node type strings)
+ */
+
+const C_STRING = 'string';
+const C_ESCAPED_SINGLE_QUOTE = 'escapedSingleQuote';
+const C_ESCAPED_DOUBLE_QUOTE = 'escapedDoubleQuote';
+const C_SINGLE_QUOTE = 'singleQuote';
+const C_DOUBLE_QUOTE = 'doubleQuote';
+const C_NEWLINE = 'newline';
+const C_SINGLE = 'single';
+
+/*
+ * Literals
+ */
+
+const L_SINGLE_QUOTE = `'`;
+const L_DOUBLE_QUOTE = `"`;
+const L_NEWLINE = `\\\n`;
+
+/*
+ * Parser nodes
+ */
+
+const T_ESCAPED_SINGLE_QUOTE = { type: C_ESCAPED_SINGLE_QUOTE, value: `\\'` };
+const T_ESCAPED_DOUBLE_QUOTE = { type: C_ESCAPED_DOUBLE_QUOTE, value: `\\"` };
+const T_SINGLE_QUOTE = { type: C_SINGLE_QUOTE, value: L_SINGLE_QUOTE };
+const T_DOUBLE_QUOTE = { type: C_DOUBLE_QUOTE, value: L_DOUBLE_QUOTE };
+const T_NEWLINE = { type: C_NEWLINE, value: L_NEWLINE };
+
+/** @typedef {T_ESCAPED_SINGLE_QUOTE | T_ESCAPED_DOUBLE_QUOTE | T_SINGLE_QUOTE | T_NEWLINE} StringAstNode */
+/**
+ * @typedef {{nodes: StringAstNode[],
+ * types: {escapedSingleQuote: number, escapedDoubleQuote: number, singleQuote: number, doubleQuote: number},
+ * quotes: boolean}} StringAst
+ */
+
+/**
+ * @param {StringAst} ast
+ * @return {string}
+ */
+function stringify(ast) {
+ return ast.nodes.reduce((str, { value }) => {
+ // Collapse multiple line strings automatically
+ if (value === L_NEWLINE) {
+ return str;
+ }
+
+ return str + value;
+ }, '');
+}
+
+/**
+ * @param {string} str
+ * @return {StringAst}
+ */
+function parse(str) {
+ let code, next, value;
+ let pos = 0;
+ let len = str.length;
+
+ /** @type StringAst */
+ const ast = {
+ nodes: [],
+ types: {
+ escapedSingleQuote: 0,
+ escapedDoubleQuote: 0,
+ singleQuote: 0,
+ doubleQuote: 0,
+ },
+ quotes: false,
+ };
+
+ while (pos < len) {
+ code = str.charCodeAt(pos);
+
+ switch (code) {
+ case SPACE:
+ case TAB:
+ case CR:
+ case FEED:
+ next = pos;
+
+ do {
+ next += 1;
+ code = str.charCodeAt(next);
+ } while (
+ code === SPACE ||
+ code === NEWLINE ||
+ code === TAB ||
+ code === CR ||
+ code === FEED
+ );
+
+ ast.nodes.push({
+ type: 'space',
+ value: str.slice(pos, next),
+ });
+ pos = next - 1;
+ break;
+ case SINGLE_QUOTE:
+ ast.nodes.push(T_SINGLE_QUOTE);
+ ast.types[C_SINGLE_QUOTE]++;
+ ast.quotes = true;
+ break;
+ case DOUBLE_QUOTE:
+ ast.nodes.push(T_DOUBLE_QUOTE);
+ ast.types[C_DOUBLE_QUOTE]++;
+ ast.quotes = true;
+ break;
+ case BACKSLASH:
+ next = pos + 1;
+
+ if (str.charCodeAt(next) === SINGLE_QUOTE) {
+ ast.nodes.push(T_ESCAPED_SINGLE_QUOTE);
+ ast.types[C_ESCAPED_SINGLE_QUOTE]++;
+ ast.quotes = true;
+ pos = next;
+ break;
+ } else if (str.charCodeAt(next) === DOUBLE_QUOTE) {
+ ast.nodes.push(T_ESCAPED_DOUBLE_QUOTE);
+ ast.types[C_ESCAPED_DOUBLE_QUOTE]++;
+ ast.quotes = true;
+ pos = next;
+ break;
+ } else if (str.charCodeAt(next) === NEWLINE) {
+ ast.nodes.push(T_NEWLINE);
+ pos = next;
+ break;
+ }
+ /*
+ * We need to fall through here to handle the token as
+ * a whole word. The missing 'break' is intentional.
+ */
+ default:
+ WORD_END.lastIndex = pos + 1;
+ WORD_END.test(str);
+
+ if (WORD_END.lastIndex === 0) {
+ next = len - 1;
+ } else {
+ next = WORD_END.lastIndex - 2;
+ }
+
+ value = str.slice(pos, next + 1);
+
+ ast.nodes.push({
+ type: C_STRING,
+ value,
+ });
+
+ pos = next;
+ }
+ pos++;
+ }
+
+ return ast;
+}
+
+/**
+ * @param {valueParser.StringNode} node
+ * @param {StringAst} ast
+ * @return {void}
+ */
+function changeWrappingQuotes(node, ast) {
+ const { types } = ast;
+
+ if (types[C_SINGLE_QUOTE] || types[C_DOUBLE_QUOTE]) {
+ return;
+ }
+
+ if (
+ node.quote === L_SINGLE_QUOTE &&
+ types[C_ESCAPED_SINGLE_QUOTE] > 0 &&
+ !types[C_ESCAPED_DOUBLE_QUOTE]
+ ) {
+ node.quote = L_DOUBLE_QUOTE;
+ }
+
+ if (
+ node.quote === L_DOUBLE_QUOTE &&
+ types[C_ESCAPED_DOUBLE_QUOTE] > 0 &&
+ !types[C_ESCAPED_SINGLE_QUOTE]
+ ) {
+ node.quote = L_SINGLE_QUOTE;
+ }
+
+ ast.nodes = changeChildQuotes(ast.nodes, node.quote);
+}
+/**
+ * @param {StringAstNode[]} childNodes
+ * @param {string} parentQuote
+ * @return {StringAstNode[]}
+ */
+function changeChildQuotes(childNodes, parentQuote) {
+ const updatedChildren = [];
+ for (const child of childNodes) {
+ if (
+ child.type === C_ESCAPED_DOUBLE_QUOTE &&
+ parentQuote === L_SINGLE_QUOTE
+ ) {
+ updatedChildren.push(T_DOUBLE_QUOTE);
+ } else if (
+ child.type === C_ESCAPED_SINGLE_QUOTE &&
+ parentQuote === L_DOUBLE_QUOTE
+ ) {
+ updatedChildren.push(T_SINGLE_QUOTE);
+ } else {
+ updatedChildren.push(child);
+ }
+ }
+ return updatedChildren;
+}
+
+/**
+ * @param {string} value
+ * @param {'single' | 'double'} preferredQuote
+ * @return {string}
+ */
+function normalize(value, preferredQuote) {
+ if (!value || !value.length) {
+ return value;
+ }
+
+ return valueParser(value)
+ .walk((child) => {
+ if (child.type !== C_STRING) {
+ return;
+ }
+
+ const ast = parse(child.value);
+
+ if (ast.quotes) {
+ changeWrappingQuotes(child, ast);
+ } else if (preferredQuote === C_SINGLE) {
+ child.quote = L_SINGLE_QUOTE;
+ } else {
+ child.quote = L_DOUBLE_QUOTE;
+ }
+
+ child.value = stringify(ast);
+ })
+ .toString();
+}
+
+/**
+ * @param {string} original
+ * @param {Map<string, string>} cache
+ * @param {'single' | 'double'} preferredQuote
+ * @return {string}
+ */
+function minify(original, cache, preferredQuote) {
+ const key = original + '|' + preferredQuote;
+ if (cache.has(key)) {
+ return /** @type {string} */ (cache.get(key));
+ }
+ const newValue = normalize(original, preferredQuote);
+ cache.set(key, newValue);
+ return newValue;
+}
+
+/** @typedef {{preferredQuote?: 'double' | 'single'}} Options */
+/**
+ * @type {import('postcss').PluginCreator<Options>}
+ * @param {Options} opts
+ * @return {import('postcss').Plugin}
+ */
+function pluginCreator(opts) {
+ const { preferredQuote } = Object.assign(
+ {},
+ {
+ preferredQuote: 'double',
+ },
+ opts
+ );
+
+ return {
+ postcssPlugin: 'postcss-normalize-string',
+
+ OnceExit(css) {
+ const cache = new Map();
+
+ css.walk((node) => {
+ switch (node.type) {
+ case 'rule':
+ node.selector = minify(node.selector, cache, preferredQuote);
+ break;
+ case 'decl':
+ node.value = minify(node.value, cache, preferredQuote);
+ break;
+ case 'atrule':
+ node.params = minify(node.params, cache, preferredQuote);
+ break;
+ }
+ });
+ },
+ };
+}
+
+pluginCreator.postcss = true;
+module.exports = pluginCreator;