added all files to project

This commit is contained in:
2022-03-10 10:36:59 +01:00
parent 09dd957b33
commit 46a936d7de
25351 changed files with 3883356 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
'use strict';
const debug = require('debug')('stylelint:file-cache');
const fileEntryCache = require('file-entry-cache');
const getCacheFile = require('./getCacheFile');
const path = require('path');
const DEFAULT_CACHE_LOCATION = './.stylelintcache';
const DEFAULT_HASH = '';
/** @typedef {import('file-entry-cache').FileDescriptor["meta"] & { hashOfConfig?: string }} CacheMetadata */
/**
* @param {string} [cacheLocation]
* @param {string} [hashOfConfig]
* @constructor
*/
class FileCache {
constructor(cacheLocation = DEFAULT_CACHE_LOCATION, hashOfConfig = DEFAULT_HASH) {
const cacheFile = path.resolve(getCacheFile(cacheLocation, process.cwd()));
debug(`Cache file is created at ${cacheFile}`);
this._fileCache = fileEntryCache.create(cacheFile);
this._hashOfConfig = hashOfConfig;
}
/**
* @param {string} absoluteFilepath
* @return {boolean}
*/
hasFileChanged(absoluteFilepath) {
// Get file descriptor compares current metadata against cached
// one and stores the result to "changed" prop.w
const descriptor = this._fileCache.getFileDescriptor(absoluteFilepath);
/** @type {CacheMetadata} */
const meta = descriptor.meta || {};
const changed = descriptor.changed || meta.hashOfConfig !== this._hashOfConfig;
if (!changed) {
debug(`Skip linting ${absoluteFilepath}. File hasn't changed.`);
}
// Mutate file descriptor object and store config hash to each file.
// Running lint with different config should invalidate the cache.
if (meta.hashOfConfig !== this._hashOfConfig) {
meta.hashOfConfig = this._hashOfConfig;
}
return changed;
}
reconcile() {
this._fileCache.reconcile();
}
destroy() {
this._fileCache.destroy();
}
/**
* @param {string} absoluteFilepath
*/
removeEntry(absoluteFilepath) {
this._fileCache.removeEntry(absoluteFilepath);
}
}
module.exports = FileCache;
+30
View File
@@ -0,0 +1,30 @@
'use strict';
const _ = require('lodash');
/** @typedef {import('postcss').ChildNode} ChildNode */
/**
* Add an empty line after a node. Mutates the node.
*
* @param {ChildNode} node
* @param {'\n' | '\r\n'} newline
* @returns {ChildNode}
*/
function addEmptyLineAfter(node, newline) {
if (node.raws.after === undefined) {
return node;
}
const after = _.last(node.raws.after.split(';')) || '';
if (!/\r?\n/.test(after)) {
node.raws.after += newline.repeat(2);
} else {
node.raws.after = node.raws.after.replace(/(\r?\n)/, `${newline}$1`);
}
return node;
}
module.exports = addEmptyLineAfter;
+26
View File
@@ -0,0 +1,26 @@
'use strict';
/** @typedef {import('postcss').ChildNode} ChildNode */
/**
* Add an empty line before a node. Mutates the node.
*
* @param {ChildNode} node
* @param {'\n' | '\r\n'} newline
* @returns {ChildNode}
*/
function addEmptyLineBefore(node, newline) {
if (node.raws.before === undefined) {
return node;
}
if (!/\r?\n/.test(node.raws.before)) {
node.raws.before = newline.repeat(2) + node.raws.before;
} else {
node.raws.before = node.raws.before.replace(/(\r?\n)/, `${newline}$1`);
}
return node;
}
module.exports = addEmptyLineBefore;
+16
View File
@@ -0,0 +1,16 @@
'use strict';
/**
* @param {import('postcss').AtRule} atRule
* @returns {number}
*/
module.exports = function (atRule) {
// Initial 1 is for the `@`
let index = 1 + atRule.name.length;
if (atRule.raws.afterName) {
index += atRule.raws.afterName.length;
}
return index;
};
+54
View File
@@ -0,0 +1,54 @@
'use strict';
/** @typedef {import('postcss').Rule} Rule */
/** @typedef {import('postcss').AtRule} AtRule */
/**
* @param {Rule | AtRule} statement
* @param {{
* noRawBefore?: boolean
* }} options
*
* @returns {string}
*/
module.exports = function (statement, options = {}) {
let result = '';
/** @type {Rule | undefined} */
let rule; /*?: postcss$rule*/
/** @type {AtRule | undefined} */
let atRule; /*?: postcss$atRule*/
if (statement.type === 'rule') {
rule = statement;
}
if (statement.type === 'atrule') {
atRule = statement;
}
if (!rule && !atRule) {
return result;
}
const before = statement.raws.before || '';
if (!options.noRawBefore) {
result += before;
}
if (rule) {
result += rule.selector;
}
if (atRule) {
result += `@${atRule.name}${atRule.raws.afterName || ''}${atRule.params}`;
}
const between = statement.raws.between;
if (between !== undefined) {
result += between;
}
return result;
};
+25
View File
@@ -0,0 +1,25 @@
'use strict';
const beforeBlockString = require('./beforeBlockString');
const hasBlock = require('./hasBlock');
const rawNodeString = require('./rawNodeString');
/** @typedef {import('postcss').Rule} Rule */
/** @typedef {import('postcss').AtRule} AtRule */
/**
* Return a CSS statement's block -- the string that starts and `{` and ends with `}`.
*
* If the statement has no block (e.g. `@import url(foo.css);`),
* return false.
*
* @param {Rule | AtRule} statement - postcss rule or at-rule node
* @return {string | boolean}
*/
module.exports = function (statement) {
if (!hasBlock(statement)) {
return false;
}
return rawNodeString(statement).slice(beforeBlockString(statement).length);
};
+10
View File
@@ -0,0 +1,10 @@
'use strict';
/**
* @param {string} source
*
* @returns {string}
*/
module.exports = function (source, blurChar = '`') {
return source.replace(/\/\*.*\*\//g, blurChar);
};
+53
View File
@@ -0,0 +1,53 @@
'use strict';
const balancedMatch = require('balanced-match');
/**
* Replace all of the characters that are arguments to a certain
* CSS function with some innocuous character.
*
* This is useful if you need to use a RegExp to find a string
* but want to ignore matches in certain functions (e.g. `url()`,
* which might contain all kinds of false positives).
*
* For example:
* blurFunctionArguments("abc url(abc) abc", "url") === "abc url(```) abc"
*
* @param {string} source
* @param {string} functionName
* @return {string} - The result string, with the function arguments "blurred"
*/
module.exports = function (source, functionName, blurChar = '`') {
const nameWithParen = `${functionName.toLowerCase()}(`;
const lowerCaseSource = source.toLowerCase();
if (!lowerCaseSource.includes(nameWithParen)) {
return source;
}
const functionNameLength = functionName.length;
let result = source;
let searchStartIndex = 0;
while (lowerCaseSource.includes(nameWithParen, searchStartIndex)) {
const openingParenIndex =
lowerCaseSource.indexOf(nameWithParen, searchStartIndex) + functionNameLength;
const parensMatch = balancedMatch('(', ')', lowerCaseSource.slice(openingParenIndex));
if (!parensMatch) {
throw new Error(`No parens match: "${source}"`);
}
const closingParenIndex = parensMatch.end + openingParenIndex;
const argumentsLength = closingParenIndex - openingParenIndex - 1;
result =
result.slice(0, openingParenIndex + 1) +
blurChar.repeat(argumentsLength) +
result.slice(closingParenIndex);
searchStartIndex = closingParenIndex;
}
return result;
};
+10
View File
@@ -0,0 +1,10 @@
'use strict';
/**
* @param {string} source
*
* @returns {string}
*/
module.exports = function (source, blurChar = ' ') {
return source.replace(/[#@{}]+/g, blurChar);
};
+52
View File
@@ -0,0 +1,52 @@
'use strict';
const normalizeRuleSettings = require('../normalizeRuleSettings');
const Result = require('postcss/lib/result');
const rules = require('../rules');
/**
* Useful for third-party code (e.g. plugins) to run a PostCSS Root
* against a specific rule and do something with the warnings
* @template T
* @template {Object} O
* @param {{
ruleName: string,
ruleSettings: import('stylelint').StylelintConfigRuleSettings<T, O>,
root: import('postcss').Root,
}} options
* @param {Function} callback
* @returns {void}
*/
module.exports = function (options, callback) {
if (!options)
throw new Error(
"checkAgainstRule requires an options object with 'ruleName', 'ruleSettings', and 'root' properties",
);
if (!callback) throw new Error('checkAgainstRule requires a callback');
if (!options.ruleName) throw new Error("checkAgainstRule requires a 'ruleName' option");
if (!Object.keys(rules).includes(options.ruleName))
throw new Error(`Rule '${options.ruleName}' does not exist`);
if (!options.ruleSettings) throw new Error("checkAgainstRule requires a 'ruleSettings' option");
if (!options.root) throw new Error("checkAgainstRule requires a 'root' option");
const settings = normalizeRuleSettings(options.ruleSettings, options.ruleName);
if (!settings) {
return;
}
// @ts-expect-error - this error should not occur with PostCSS 8
const tmpPostcssResult = new Result();
rules[options.ruleName](
settings[0],
/** @type {O} */ (settings[1]),
{},
)(options.root, tmpPostcssResult);
tmpPostcssResult.warnings().forEach(callback);
};
+92
View File
@@ -0,0 +1,92 @@
'use strict';
const _ = require('lodash');
const chalk = require('chalk');
const EOL = require('os').EOL;
const levenshtein = require('fastest-levenshtein');
/**
* @param {{ [key: string]: { alias?: string } }} allowedOptions
* @return {string[]}
*/
const buildAllowedOptions = (allowedOptions) => {
let options = Object.keys(allowedOptions);
options = options.reduce((opts, opt) => {
const alias = allowedOptions[opt].alias;
if (alias) {
opts.push(alias);
}
return opts;
}, options);
options.sort();
return options;
};
/**
* @param {string[]} all
* @param {string} invalid
* @return {null|string}
*/
const suggest = (all, invalid) => {
const maxThreshold = 10;
for (let threshold = 1; threshold <= maxThreshold; threshold++) {
const suggestion = all.find((option) => levenshtein.distance(option, invalid) <= threshold);
if (suggestion) {
return suggestion;
}
}
return null;
};
/**
* @param {string} opt
* @return {string}
*/
const cliOption = (opt) => {
if (opt.length === 1) {
return `"-${opt}"`;
}
return `"--${_.kebabCase(opt)}"`;
};
/**
* @param {string} invalid
* @param {string|null} suggestion
* @return {string}
*/
const buildMessageLine = (invalid, suggestion) => {
let line = `Invalid option ${chalk.red(cliOption(invalid))}.`;
if (suggestion) {
line += ` Did you mean ${chalk.cyan(cliOption(suggestion))}?`;
}
return line + EOL;
};
/**
* @param {{ [key: string]: any }} allowedOptions
* @param {{ [key: string]: any }} inputOptions
* @return {string}
*/
module.exports = function checkInvalidCLIOptions(allowedOptions, inputOptions) {
const allOptions = buildAllowedOptions(allowedOptions);
return Object.keys(inputOptions)
.filter((opt) => !allOptions.includes(opt))
.map(_.kebabCase)
.reduce((msg, invalid) => {
// NOTE: No suggestion for shortcut options because it's too difficult
const suggestion = invalid.length >= 2 ? suggest(allOptions, invalid) : null;
return msg + buildMessageLine(invalid, suggestion);
}, '');
};
+15
View File
@@ -0,0 +1,15 @@
'use strict';
/**
* Create configurationError from text and set CLI exit code
* @param {string} text
* @returns {Object}
*/
module.exports = function (text) /* Object */ {
/** @type {Error & {code?: number}} */
const err = new Error(text);
err.code = 78;
return err;
};
+56
View File
@@ -0,0 +1,56 @@
'use strict';
const _ = require('lodash');
/** @typedef {false | { match: string, pattern: string }} ReturnValue */
/**
* Checks if a string contains a value. The comparison value can be a string or
* an array of strings.
*
* Any strings starting and ending with `/` are ignored. Use the
* matchesStringOrRegExp() util to match regexes.
*
* @param {string} input
* @param {string | string[]} comparison
*
* @returns {ReturnValue}
*/
module.exports = function containsString(input, comparison) {
if (!Array.isArray(comparison)) {
return testAgainstString(input, comparison);
}
for (const comparisonItem of comparison) {
const testResult = testAgainstString(input, comparisonItem);
if (testResult) {
return testResult;
}
}
return false;
};
/**
*
* @param {string} value
* @param {string} comparison
*
* @returns {ReturnValue}
*/
function testAgainstString(value, comparison) {
if (!comparison) return false;
if (!_.isString(comparison)) return false;
if (comparison.startsWith('/') && comparison.endsWith('/')) {
return false;
}
if (value.includes(comparison)) {
return { match: value, pattern: comparison };
}
return false;
}
+26
View File
@@ -0,0 +1,26 @@
'use strict';
const _ = require('lodash');
/**
* Get the index of a declaration's value
*
* @param {import('postcss').Declaration} decl
*
* @returns {number}
*/
module.exports = function (decl) {
return [
_.get(decl, 'raws.prop.prefix'),
_.get(decl, 'raws.prop.raw', decl.prop),
_.get(decl, 'raws.prop.suffix'),
_.get(decl, 'raws.between', ':'),
_.get(decl, 'raws.value.prefix'),
].reduce((count, str) => {
if (str) {
return count + str.length;
}
return count;
}, 0);
};
+59
View File
@@ -0,0 +1,59 @@
'use strict';
const { isRoot, isAtRule, isRule } = require('./typeGuards');
/** @typedef {import('postcss').Root} Root */
/** @typedef {import('postcss').Root} Document */
/** @typedef {import('postcss').Node} PostcssNode */
/** @typedef {import('postcss').ContainerBase} PostcssContainerNode */
/**
* @param {PostcssNode} node
* @returns {node is PostcssContainerNode}
*/
function isContainerNode(node) {
return isRule(node) || isAtRule(node) || isRoot(node);
}
/**
* In order to accommodate nested blocks (postcss-nested),
* we need to run a shallow loop (instead of eachDecl() or eachRule(),
* which loop recursively) and allow each nested block to accumulate
* its own list of properties -- so that a property in a nested rule
* does not conflict with the same property in the parent rule
* executes a provided function once for each declaration block.
*
* @param {Root | Document} root - root element of file.
* @param {function} cb - Function to execute for each declaration block
*
* @returns {void}
*/
module.exports = function (root, cb) {
/**
* @param {PostcssNode} statement
*
* @returns {void}
*/
function each(statement) {
if (!isContainerNode(statement)) return;
if (statement.nodes && statement.nodes.length) {
/** @type {PostcssNode[]} */
const decls = [];
statement.nodes.forEach((node) => {
if (node.type === 'decl') {
decls.push(node);
}
each(node);
});
if (decls.length) {
cb(decls.forEach.bind(decls));
}
}
}
each(root);
};
+19
View File
@@ -0,0 +1,19 @@
'use strict';
const { isPathValid } = require('ignore').default;
/**
* @param {import('ignore').Ignore} ignorer
* @param {string[]} filePaths
* @returns {string[]}
*/
module.exports = function filterFilePaths(ignorer, filePaths) {
const validForIgnore = filePaths.filter(isPathValid);
// Paths which starts with `..` are not valid for `ignore`, e. g. `../style.css`
const notValidForIgnore = new Set(filePaths.filter((p) => !validForIgnore.includes(p)));
const filteredByIgnore = new Set(ignorer.filter(validForIgnore));
// Preserving files order, while removing paths which were filtered by `ignore`
return filePaths.filter((p) => notValidForIgnore.has(p) || filteredByIgnore.has(p));
};
+69
View File
@@ -0,0 +1,69 @@
'use strict';
const getUnitFromValueNode = require('./getUnitFromValueNode');
const isStandardSyntaxValue = require('./isStandardSyntaxValue');
const isVariable = require('./isVariable');
const keywordSets = require('../reference/keywordSets');
const postcssValueParser = require('postcss-value-parser');
/** @typedef {import('postcss-value-parser').Node} Node */
/**
* Get the animation name within an `animation` shorthand property value.
*
* @param {string} value
*
* @returns {Node[]}
*/
module.exports = function findAnimationName(value) {
/** @type {Node[]} */
const animationNames = [];
const valueNodes = postcssValueParser(value);
// Handle `inherit`, `initial` and etc
if (
valueNodes.nodes.length === 1 &&
keywordSets.basicKeywords.has(valueNodes.nodes[0].value.toLowerCase())
) {
return [valueNodes.nodes[0]];
}
valueNodes.walk((valueNode) => {
if (valueNode.type === 'function') {
return false;
}
if (valueNode.type !== 'word') {
return;
}
const valueLowerCase = valueNode.value.toLowerCase();
// Ignore non-standard syntax
if (!isStandardSyntaxValue(valueLowerCase)) {
return;
}
// Ignore variables
if (isVariable(valueLowerCase)) {
return;
}
// Ignore keywords for other animation parts
if (keywordSets.animationShorthandKeywords.has(valueLowerCase)) {
return;
}
// Ignore numbers with units
const unit = getUnitFromValueNode(valueNode);
if (unit || unit === '') {
return;
}
animationNames.push(valueNode);
});
return animationNames;
};
+23
View File
@@ -0,0 +1,23 @@
'use strict';
/**
* Find the at-rule in which a rule is nested.
*
* Returns `null` if the rule is not nested within an at-rule.
*
* @param {import('postcss').Rule} rule
* @returns {null | import('postcss').AtRule}
*/
module.exports = function findAtRuleContext(rule) {
const parent = rule.parent;
if (parent.type === 'root') {
return null;
}
if (parent.type === 'atrule') {
return parent;
}
return findAtRuleContext(parent);
};
+128
View File
@@ -0,0 +1,128 @@
'use strict';
const isNumbery = require('./isNumbery');
const isStandardSyntaxValue = require('./isStandardSyntaxValue');
const isValidFontSize = require('./isValidFontSize');
const isVariable = require('./isVariable');
const keywordSets = require('../reference/keywordSets');
const postcssValueParser = require('postcss-value-parser');
const nodeTypesToCheck = new Set(['word', 'string', 'space', 'div']);
/** @typedef {import('postcss-value-parser').Node} Node */
/**
*
* @param {Node} firstNode
* @param {Node} secondNode
* @param {string | null} charactersBetween
*
* @returns {Node}
*/
function joinValueNodes(firstNode, secondNode, charactersBetween) {
firstNode.value = firstNode.value + charactersBetween + secondNode.value;
return firstNode;
}
/**
* Get the font-families within a `font` shorthand property value.
*
* @param {string} value
* @return {object} Collection font-family nodes
*/
module.exports = function findFontFamily(value) {
/** @type {Node[]} */
const fontFamilies = [];
const valueNodes = postcssValueParser(value);
// Handle `inherit`, `initial` and etc
if (
valueNodes.nodes.length === 1 &&
keywordSets.basicKeywords.has(valueNodes.nodes[0].value.toLowerCase())
) {
return [valueNodes.nodes[0]];
}
let needMergeNodesByValue = false;
/** @type {string | null} */
let mergeCharacters = null;
valueNodes.walk((valueNode, index, nodes) => {
if (valueNode.type === 'function') {
return false;
}
if (!nodeTypesToCheck.has(valueNode.type)) {
return;
}
const valueLowerCase = valueNode.value.toLowerCase();
// Ignore non standard syntax
if (!isStandardSyntaxValue(valueLowerCase)) {
return;
}
// Ignore variables
if (isVariable(valueLowerCase)) {
return;
}
// Ignore keywords for other font parts
if (
keywordSets.fontShorthandKeywords.has(valueLowerCase) &&
!keywordSets.fontFamilyKeywords.has(valueLowerCase)
) {
return;
}
// Ignore font-sizes
if (isValidFontSize(valueNode.value)) {
return;
}
// Ignore anything come after a <font-size>/, because it's a line-height
if (
nodes[index - 1] &&
nodes[index - 1].value === '/' &&
nodes[index - 2] &&
isValidFontSize(nodes[index - 2].value)
) {
return;
}
// Ignore number values
if (isNumbery(valueLowerCase)) {
return;
}
// Detect when a space or comma is dividing a list of font-families, and save the joining character.
if (
(valueNode.type === 'space' || (valueNode.type === 'div' && valueNode.value !== ',')) &&
fontFamilies.length !== 0
) {
needMergeNodesByValue = true;
mergeCharacters = valueNode.value;
return;
}
if (valueNode.type === 'space' || valueNode.type === 'div') {
return;
}
const fontFamily = valueNode;
if (needMergeNodesByValue) {
joinValueNodes(fontFamilies[fontFamilies.length - 1], valueNode, mergeCharacters);
needMergeNodesByValue = false;
mergeCharacters = null;
} else {
fontFamilies.push(fontFamily);
}
});
return fontFamilies;
};
+60
View File
@@ -0,0 +1,60 @@
'use strict';
const isStandardSyntaxValue = require('./isStandardSyntaxValue');
const isVariable = require('./isVariable');
const keywordSets = require('../reference/keywordSets');
const postcssValueParser = require('postcss-value-parser');
/**
* Get the list-style-type within a `list-style` shorthand property value.
*
* @param {string} value
*/
module.exports = function findListStyleType(value) {
/** @type {Array<import('postcss-value-parser').WordNode>} */
const listStyleTypes = [];
const valueNodes = postcssValueParser(value);
// Handle `inherit`, `initial` and etc
if (
valueNodes.nodes.length === 1 &&
keywordSets.listStyleTypeKeywords.has(valueNodes.nodes[0].value.toLowerCase())
) {
return [valueNodes.nodes[0]];
}
valueNodes.walk((valueNode) => {
if (valueNode.type === 'function') {
return false;
}
if (valueNode.type !== 'word') {
return;
}
const valueLowerCase = valueNode.value.toLowerCase();
// Ignore non standard syntax
if (!isStandardSyntaxValue(valueLowerCase)) {
return;
}
// Ignore variables
if (isVariable(valueLowerCase)) {
return;
}
// Ignore keywords for other font parts
if (
keywordSets.listStylePositionKeywords.has(valueLowerCase) ||
keywordSets.listStyleImageKeywords.has(valueLowerCase)
) {
return;
}
listStyleTypes.push(valueNode);
});
return listStyleTypes;
};
+41
View File
@@ -0,0 +1,41 @@
'use strict';
const balancedMatch = require('balanced-match');
const styleSearch = require('style-search');
/**
* Search a CSS string for functions by name.
* For every match, invoke the callback, passing the function's
* "argument(s) string" (whatever is inside the parentheses)
* as an argument.
*
* Callback will be called once for every matching function found,
* with the function's "argument(s) string" and its starting index
* as the arguments.
*
* @param {string} source
* @param {string} functionName
* @param {Function} callback
*/
module.exports = function (source, functionName, callback) {
styleSearch(
{
source,
target: functionName,
functionNames: 'check',
},
(match) => {
if (source[match.endIndex] !== '(') {
return;
}
const parensMatch = balancedMatch('(', ')', source.substr(match.startIndex));
if (!parensMatch) {
throw new Error(`No parens match: "${source}"`);
}
callback(parensMatch.body, match.endIndex + 1);
},
);
};
+11
View File
@@ -0,0 +1,11 @@
'use strict';
const _ = require('lodash');
/**
* @param {import('postcss').AtRule} atRule
* @returns {string}
*/
module.exports = function getAtRuleParams(atRule) {
return _.get(atRule, 'raws.params.raw', atRule.params);
};
+52
View File
@@ -0,0 +1,52 @@
'use strict';
const fs = require('fs');
const hash = require('./hash');
const path = require('path');
/**
* Return the cacheFile to be used by stylelint, based on whether the provided parameter is
* a directory or looks like a directory (ends in `path.sep`), in which case the file
* name will be `cacheFile/.cache_hashOfCWD`.
*
* If cacheFile points to a file or looks like a file, then it will just use that file.
*
* @param {string} cacheFile - The name of file to be used to store the cache
* @param {string} cwd - Current working directory. Used for tests
* @returns {string} Resolved path to the cache file
*/
module.exports = function getCacheFile(cacheFile, cwd) {
/*
* Make sure path separators are normalized for environment/os.
* Also, keep trailing path separator if present.
*/
cacheFile = path.normalize(cacheFile);
const resolvedCacheFile = path.resolve(cwd, cacheFile);
// If the last character passed is a path separator, we assume is a directory.
const looksLikeADirectory = cacheFile[cacheFile.length - 1] === path.sep;
/**
* Return the default cache file name when provided parameter is a directory.
* @returns {string} - Resolved path to the cacheFile
*/
function getCacheFileForDirectory() {
return path.join(resolvedCacheFile, `.stylelintcache_${hash(cwd)}`);
}
let fileStats;
try {
fileStats = fs.lstatSync(resolvedCacheFile);
} catch {
fileStats = null;
}
if (looksLikeADirectory || (fileStats && fileStats.isDirectory())) {
// Return path to provided directory with generated file name.
return getCacheFileForDirectory();
}
// Return normalized path to cache file.
return resolvedCacheFile;
};
+11
View File
@@ -0,0 +1,11 @@
'use strict';
const _ = require('lodash');
/**
* @param {import('postcss').Declaration} decl
* @returns {string}
*/
module.exports = function getDeclarationValue(decl) {
return _.get(decl, 'raws.value.raw', decl.value);
};
+34
View File
@@ -0,0 +1,34 @@
'use strict';
// Try to get file ignorer from '.stylelintignore'
const fs = require('fs');
const path = require('path');
const { default: ignore } = require('ignore');
const DEFAULT_IGNORE_FILENAME = '.stylelintignore';
const FILE_NOT_FOUND_ERROR_CODE = 'ENOENT';
/** @typedef {import('stylelint').StylelintStandaloneOptions} StylelintOptions */
/**
* @param {StylelintOptions} options
* @return {import('ignore').Ignore}
*/
module.exports = function (options) {
const ignoreFilePath = options.ignorePath || DEFAULT_IGNORE_FILENAME;
const absoluteIgnoreFilePath = path.isAbsolute(ignoreFilePath)
? ignoreFilePath
: path.resolve(process.cwd(), ignoreFilePath);
let ignoreText = '';
try {
ignoreText = fs.readFileSync(absoluteIgnoreFilePath, 'utf8');
} catch (readError) {
if (readError.code !== FILE_NOT_FOUND_ERROR_CODE) throw readError;
}
const ignorePattern = options.ignorePattern || [];
const ignorer = ignore().add(ignoreText).add(ignorePattern);
return ignorer;
};
+19
View File
@@ -0,0 +1,19 @@
'use strict';
const formatters = require('../formatters');
/**
* @param {{ useOr?: boolean }} [options={}]
* @returns {string}
*/
module.exports = function getFormatterOptionsText(options = {}) {
let output = Object.keys(formatters)
.map((name) => `"${name}"`)
.join(', ');
if (options.useOr) {
output = output.replace(/, ([a-z"]+)$/u, ' or $1');
}
return output;
};
+31
View File
@@ -0,0 +1,31 @@
'use strict';
const configurationError = require('./configurationError');
const globalModules = require('global-modules');
const resolveFrom = require('resolve-from');
/**
* @param {string} basedir
* @param {string} lookup
* @return {string}
*/
module.exports = function getModulePath(basedir, lookup) {
// 1. Try to resolve from the provided directory
// 2. Try to resolve from `process.cwd`
// 3. Try to resolve from global `node_modules` directory
let path = resolveFrom.silent(basedir, lookup);
if (!path) {
path = resolveFrom.silent(process.cwd(), lookup);
}
if (!path) {
path = resolveFrom.silent(globalModules, lookup);
}
if (!path) {
throw configurationError(`Could not find "${lookup}". Do you need a \`configBasedir\`?`);
}
return path;
};
+38
View File
@@ -0,0 +1,38 @@
'use strict';
const _ = require('lodash');
/** @typedef {import('postcss').Node} Node */
/**
* @param {Node | void} node
*/
function getNodeLine(node) {
return _.get(node, 'source.start.line');
}
/**
* @param {Node | void} node
* @returns {Node | void}
*/
module.exports = function getNextNonSharedLineCommentNode(node) {
if (node === undefined) {
return undefined;
}
/** @type {Node | void} */
const nextNode = node.next();
if (_.get(nextNode, 'type') !== 'comment') {
return nextNode;
}
if (
getNodeLine(node) === getNodeLine(nextNode) ||
(nextNode !== undefined && getNodeLine(nextNode) === getNodeLine(nextNode.next()))
) {
return getNextNonSharedLineCommentNode(nextNode);
}
return nextNode;
};
+11
View File
@@ -0,0 +1,11 @@
'use strict';
const os = require('os');
// This function simply provides roundabout way of getting os.EOL
// so we can mock this for Jest tests
function getOsEl() {
return os.EOL;
}
module.exports = getOsEl;
@@ -0,0 +1,40 @@
'use strict';
const _ = require('lodash');
/** @typedef {import('postcss').Node} Node */
/**
* @param {Node} node
*/
function getNodeLine(node) {
return _.get(node, 'source.start.line');
}
/**
* @param {import('postcss').Node | void} node
* @returns {Node | void}
*/
module.exports = function getPreviousNonSharedLineCommentNode(node) {
if (node === undefined) {
return undefined;
}
const previousNode = node.prev();
if (!previousNode || _.get(previousNode, 'type') !== 'comment') {
return previousNode;
}
if (getNodeLine(node) === getNodeLine(previousNode)) {
return getPreviousNonSharedLineCommentNode(previousNode);
}
const previousNode2 = previousNode.prev();
if (previousNode2 && getNodeLine(previousNode) === getNodeLine(previousNode2)) {
return getPreviousNonSharedLineCommentNode(previousNode);
}
return previousNode;
};
+13
View File
@@ -0,0 +1,13 @@
'use strict';
const _ = require('lodash');
/**
* @param {import('postcss').Rule} ruleNode
* @returns {string}
*/
function getRuleSelector(ruleNode) {
return _.get(ruleNode, 'raws.selector.raw', ruleNode.selector);
}
module.exports = getRuleSelector;
+39
View File
@@ -0,0 +1,39 @@
'use strict';
const { URL } = require('url');
/**
* Get unit from value node
*
* Returns `null` if the unit is not found.
*
* @param {string} urlString
*/
module.exports = function (urlString) {
let protocol = null;
try {
protocol = new URL(urlString).protocol;
} catch {
return null;
}
if (protocol === null || typeof protocol === 'undefined') {
return null;
}
const scheme = protocol.slice(0, -1); // strip trailing `:`
// The URL spec does not require a scheme to be followed by `//`, but checking
// for it allows this rule to differentiate <scheme>:<hostname> urls from
// <hostname>:<port> urls. `data:` scheme urls are an exception to this rule.
const slashIndex = protocol.length;
const expectedSlashes = urlString.slice(slashIndex, slashIndex + 2);
const isSchemeLessUrl = expectedSlashes !== '//' && scheme !== 'data';
if (isSchemeLessUrl) {
return null;
}
return scheme;
};
+49
View File
@@ -0,0 +1,49 @@
'use strict';
const blurInterpolation = require('./blurInterpolation');
const isStandardSyntaxValue = require('./isStandardSyntaxValue');
const valueParser = require('postcss-value-parser');
/**
* Get unit from value node
*
* Returns `null` if the unit is not found.
*
* @param {import('postcss-value-parser').Node} node
*
* @returns {string | null}
*/
module.exports = function (node) {
if (!node || !node.value) {
return null;
}
// Ignore non-word nodes
if (node.type !== 'word') {
return null;
}
// Ignore non standard syntax
if (!isStandardSyntaxValue(node.value)) {
return null;
}
// Ignore HEX
if (node.value.startsWith('#')) {
return null;
}
// Remove non standard stuff
const value = blurInterpolation(node.value, '')
// ignore hack unit
.replace('\\0', '')
.replace('\\9', '');
const parsedUnit = valueParser.unit(value);
if (!parsedUnit) {
return null;
}
return parsedUnit.unit;
};
+11
View File
@@ -0,0 +1,11 @@
'use strict';
/**
* Check if a statement has an block (empty or otherwise).
*
* @param {import('postcss').Rule | import('postcss').AtRule} statement - postcss rule or at-rule node
* @return {boolean} True if `statement` has a block (empty or otherwise)
*/
module.exports = function (statement) {
return statement.nodes !== undefined;
};
+13
View File
@@ -0,0 +1,13 @@
'use strict';
/**
* Check if a statement has an empty block.
*
* @param {import('postcss').Rule | import('postcss').AtRule} statement - postcss rule or at-rule node
* @return {boolean} True if the statement has a block and it is empty
*/
module.exports = function (statement) {
return (
statement.nodes !== undefined && statement.nodes.length === 0 // has block
); // and is empty
};
+14
View File
@@ -0,0 +1,14 @@
'use strict';
/**
* Check if a string contains at least one empty line
*
* @param {string} string
*
* @returns {boolean}
*/
module.exports = function (string) {
if (string === '' || string === undefined) return false;
return /\n[\r\t ]*\n/.test(string);
};
+26
View File
@@ -0,0 +1,26 @@
'use strict';
const hasLessInterpolation = require('../utils/hasLessInterpolation');
const hasPsvInterpolation = require('../utils/hasPsvInterpolation');
const hasScssInterpolation = require('../utils/hasScssInterpolation');
const hasTplInterpolation = require('../utils/hasTplInterpolation');
/**
* Check whether a string has interpolation
*
* @param {string} string
* @return {boolean} If `true`, a string has interpolation
*/
module.exports = function (string) {
// SCSS or Less interpolation
if (
hasLessInterpolation(string) ||
hasScssInterpolation(string) ||
hasTplInterpolation(string) ||
hasPsvInterpolation(string)
) {
return true;
}
return false;
};
+11
View File
@@ -0,0 +1,11 @@
'use strict';
/**
* Check whether a string has less interpolation
*
* @param {string} string
* @return {boolean} If `true`, a string has less interpolation
*/
module.exports = function (string) {
return /@{.+?}/.test(string);
};
+10
View File
@@ -0,0 +1,10 @@
'use strict';
/**
* Check whether a string has postcss-simple-vars interpolation
*
* @param {string} string
*/
module.exports = function (string) {
return /\$\(.+?\)/.test(string);
};
+10
View File
@@ -0,0 +1,10 @@
'use strict';
/**
* Check whether a string has scss interpolation
*
* @param {string} string
*/
module.exports = function (string) {
return /#{.+?}/.test(string);
};
+11
View File
@@ -0,0 +1,11 @@
'use strict';
/**
* Check whether a string has JS template literal interpolation or HTML-like template
*
* @param {string} string
* @return {boolean} If `true`, a string has template literal interpolation
*/
module.exports = function (string) {
return /{.+?}/.test(string);
};
+12
View File
@@ -0,0 +1,12 @@
'use strict';
const murmur = require('imurmurhash');
/**
* hash the given string
* @param {string} str the string to hash
* @returns {string} the hash
*/
module.exports = function hash(str) {
return murmur(str).result().toString(36);
};
+16
View File
@@ -0,0 +1,16 @@
'use strict';
const isSharedLineComment = require('./isSharedLineComment');
/**
* @param {import('postcss').Node} node
*/
module.exports = function (node) {
const previousNode = node.prev();
if (!previousNode || previousNode.type !== 'comment') {
return false;
}
return !isSharedLineComment(previousNode);
};
+22
View File
@@ -0,0 +1,22 @@
'use strict';
const isSharedLineComment = require('./isSharedLineComment');
/**
* @param {import('postcss').Node} node
*/
function isAfterSingleLineComment(node) {
const prevNode = node.prev();
return (
prevNode !== undefined &&
prevNode.type === 'comment' &&
!isSharedLineComment(prevNode) &&
prevNode.source &&
prevNode.source.start &&
prevNode.source.end &&
prevNode.source.start.line === prevNode.source.end.line
);
}
module.exports = isAfterSingleLineComment;
+20
View File
@@ -0,0 +1,20 @@
'use strict';
const _ = require('lodash');
const getPreviousNonSharedLineCommentNode = require('./getPreviousNonSharedLineCommentNode');
const isCustomProperty = require('./isCustomProperty');
const isStandardSyntaxDeclaration = require('./isStandardSyntaxDeclaration');
/**
* @param {import('postcss').Node} node
*/
module.exports = function (node) {
const prevNode = getPreviousNonSharedLineCommentNode(node);
return (
prevNode !== undefined &&
prevNode.type === 'decl' &&
isStandardSyntaxDeclaration(prevNode) &&
!isCustomProperty(_.get(prevNode, 'prop', ''))
);
};
+86
View File
@@ -0,0 +1,86 @@
'use strict';
const autoprefixer = require('autoprefixer');
const Browsers = require('autoprefixer/lib/browsers');
const Prefixes = require('autoprefixer/lib/prefixes');
/**
* Use Autoprefixer's secret powers to determine whether or
* not a certain CSS identifier contains a vendor prefix that
* Autoprefixer, given the standardized identifier, could add itself.
*
* Used by `*-no-vendor-prefix-*` rules to find superfluous
* vendor prefixes.
*/
const prefixes = new Prefixes(
autoprefixer.data.prefixes,
new Browsers(autoprefixer.data.browsers, []),
);
/**
* Most identifier types have to be looked up in a unique way,
* so we're exposing special functions for each.
*/
module.exports = {
/**
* @param {string} identifier
* @returns {boolean}
*/
atRuleName(identifier) {
return Boolean(prefixes.remove[`@${identifier.toLowerCase()}`]);
},
/**
* @param {string} identifier
* @returns {boolean}
*/
selector(identifier) {
return prefixes.remove.selectors.some((/** @type {{ prefixed: string}} */ selectorObj) => {
return identifier.toLowerCase() === selectorObj.prefixed;
});
},
/**
* @param {string} identifier
* @returns {boolean}
*/
mediaFeatureName(identifier) {
return identifier.toLowerCase().includes('device-pixel-ratio');
},
/**
* @param {string} identifier
* @returns {boolean}
*/
property(identifier) {
return Boolean(autoprefixer.data.prefixes[prefixes.unprefixed(identifier.toLowerCase())]);
},
/**
*
* @param {string} prop
* @param {string} value
* @returns {boolean}
*/
propertyValue(prop, value) {
const possiblePrefixableValues =
(prefixes.remove[prop.toLowerCase()] && prefixes.remove[prop.toLowerCase()].values) || false;
return (
possiblePrefixableValues &&
possiblePrefixableValues.some((/** @type {{ prefixed: string}} */ valueObj) => {
return value.toLowerCase() === valueObj.prefixed;
})
);
},
/**
*
* @param {string} value
* @returns {string}
*/
unprefix(value) {
return value.replace(/-\w+-/, '');
},
};
@@ -0,0 +1,22 @@
'use strict';
const getPreviousNonSharedLineCommentNode = require('./getPreviousNonSharedLineCommentNode');
const hasBlock = require('./hasBlock');
/**
* @param {import('postcss').AtRule} atRule
* @returns {boolean}
*/
module.exports = function (atRule) {
if (atRule.type !== 'atrule') {
return false;
}
const previousNode = getPreviousNonSharedLineCommentNode(atRule);
if (previousNode === undefined) {
return false;
}
return previousNode.type === 'atrule' && !hasBlock(previousNode) && !hasBlock(atRule);
};
@@ -0,0 +1,19 @@
'use strict';
const _ = require('lodash');
const getPreviousNonSharedLineCommentNode = require('./getPreviousNonSharedLineCommentNode');
const isBlocklessAtRuleAfterBlocklessAtRule = require('./isBlocklessAtRuleAfterBlocklessAtRule');
/**
* @param {import('postcss').AtRule} atRule
* @returns {boolean}
*/
module.exports = function (atRule) {
if (!isBlocklessAtRuleAfterBlocklessAtRule(atRule)) {
return false;
}
const previousNode = getPreviousNonSharedLineCommentNode(atRule);
return _.get(previousNode, 'name') === atRule.name;
};
+23
View File
@@ -0,0 +1,23 @@
'use strict';
const keywordSets = require('../reference/keywordSets');
/**
* Check whether a node is a context-functional pseudo-class (i.e. either a logical combination
* or a 'aNPlusBOfSNotationPseudoClasses' / tree-structural pseudo-class)
*
* @param {import('postcss-selector-parser').Pseudo} node postcss-selector-parser node (of type pseudo)
* @return {boolean} If `true`, the node is a context-functional pseudo-class
*/
module.exports = function isContextFunctionalPseudoClass(node) {
if (node.type === 'pseudo') {
const normalisedParentName = node.value.toLowerCase().replace(/:+/, '');
return (
keywordSets.logicalCombinationsPseudoClasses.has(normalisedParentName) ||
keywordSets.aNPlusBOfSNotationPseudoClasses.has(normalisedParentName)
);
}
return false;
};
+21
View File
@@ -0,0 +1,21 @@
'use strict';
const keywordSets = require('../reference/keywordSets');
/**
* Check value is a custom ident
*
* @param {string} value
*/
module.exports = function (value) {
const valueLowerCase = value.toLowerCase();
if (
keywordSets.counterIncrementKeywords.has(valueLowerCase) ||
Number.isFinite(Number.parseInt(valueLowerCase))
) {
return false;
}
return true;
};
+21
View File
@@ -0,0 +1,21 @@
'use strict';
const keywordSets = require('../reference/keywordSets');
/**
* Check value is a custom ident
*
* @param {string} value
*/
module.exports = function (value) {
const valueLowerCase = value.toLowerCase();
if (
keywordSets.counterResetKeywords.has(valueLowerCase) ||
Number.isFinite(Number.parseInt(valueLowerCase))
) {
return false;
}
return true;
};
+46
View File
@@ -0,0 +1,46 @@
'use strict';
const htmlTags = require('html-tags');
const keywordSets = require('../reference/keywordSets');
const mathMLTags = require('mathml-tag-names');
const svgTags = require('svg-tags');
/**
* Check whether a type selector is a custom element
*
* @param {string} selector
* @returns {boolean}
*/
module.exports = function (selector) {
if (!/^[a-z]/.test(selector)) {
return false;
}
if (!selector.includes('-')) {
return false;
}
const selectorLowerCase = selector.toLowerCase();
if (selectorLowerCase !== selector) {
return false;
}
if (svgTags.includes(selectorLowerCase)) {
return false;
}
if (htmlTags.includes(selectorLowerCase)) {
return false;
}
if (keywordSets.nonStandardHtmlTags.has(selectorLowerCase)) {
return false;
}
if (mathMLTags.includes(selectorLowerCase)) {
return false;
}
return true;
};
+10
View File
@@ -0,0 +1,10 @@
'use strict';
/**
* Check whether a media query is a custom
* @param {string} mediaQuery
* @returns {boolean}
*/
module.exports = function (mediaQuery) {
return mediaQuery.startsWith('--');
};
+10
View File
@@ -0,0 +1,10 @@
'use strict';
/**
* Check whether a property is a custom one
* @param {string} property
* @returns {boolean}
*/
module.exports = function (property) {
return property.startsWith('--');
};
+18
View File
@@ -0,0 +1,18 @@
'use strict';
const _ = require('lodash');
const hasBlock = require('../utils/hasBlock');
/**
* Check whether a Node is a custom property set
*
* @param {import('postcss').Rule} node
* @returns {boolean}
*/
module.exports = function (node) {
const selector = _.get(node, 'raws.selector.raw', node.selector);
return (
node.type === 'rule' && hasBlock(node) && selector.startsWith('--') && selector.endsWith(':')
);
};
+11
View File
@@ -0,0 +1,11 @@
'use strict';
/**
* Check whether a selector is a custom one
*
* @param {string} selector
* @returns {boolean}
*/
module.exports = function (selector) {
return selector.startsWith(':--');
};
+67
View File
@@ -0,0 +1,67 @@
'use strict';
const { isComment, hasSource } = require('./typeGuards');
/**
* @param {import('postcss').Node} statement
* @returns {boolean}
*/
module.exports = function (statement) {
const parentNode = statement.parent;
if (parentNode === undefined || parentNode.type === 'root') {
return false;
}
if (statement === parentNode.first) {
return true;
}
/*
* Search for the statement in the parent's nodes, ignoring comment
* nodes on the same line as the parent's opening brace.
*/
const parentNodes = parentNode.nodes;
if (!parentNodes) {
return false;
}
const firstNode = parentNodes[0];
if (
!isComment(firstNode) ||
(typeof firstNode.raws.before === 'string' && firstNode.raws.before.includes('\n'))
) {
return false;
}
if (!hasSource(firstNode) || !firstNode.source.start) {
return false;
}
const openingBraceLine = firstNode.source.start.line;
if (!firstNode.source.end || openingBraceLine !== firstNode.source.end.line) {
return false;
}
for (let i = 1; i < parentNodes.length; i++) {
const node = parentNodes[i];
if (node === statement) {
return true;
}
if (
!isComment(node) ||
(hasSource(node) && node.source.end && node.source.end.line !== openingBraceLine)
) {
return false;
}
}
/* istanbul ignore next: Should always return in the loop */
return false;
};
+15
View File
@@ -0,0 +1,15 @@
'use strict';
const { isRoot } = require('./typeGuards');
/**
* @param {import('postcss').Node} node
* @returns {boolean}
*/
module.exports = function (node) {
if (isRoot(node)) return false;
const parentNode = node.parent;
return isRoot(parentNode) && node === parentNode.first;
};
+13
View File
@@ -0,0 +1,13 @@
'use strict';
/**
* Check if a rule is a keyframe one
*
* @param {import('postcss').Rule} rule
* @returns {boolean}
*/
module.exports = function (rule) {
const parent = rule.parent;
return parent.type === 'atrule' && parent.name.toLowerCase() === 'keyframes';
};
+22
View File
@@ -0,0 +1,22 @@
'use strict';
const keywordSets = require('../reference/keywordSets');
/**
* Check whether a string is a keyframe selector.
*
* @param {string} selector
* @returns {boolean}
*/
module.exports = function (selector) {
if (keywordSets.keyframeSelectorKeywords.has(selector)) {
return true;
}
// Percentages
if (/^(?:\d+\.?\d*|\d*\.?\d+)%$/.test(selector)) {
return true;
}
return false;
};
+14
View File
@@ -0,0 +1,14 @@
'use strict';
const hasBlock = require('./hasBlock');
/**
* @param {import('postcss').AtRule | import('postcss-less').AtRule} atRule
* @returns {boolean}
*/
module.exports = function (atRule) {
return (
(atRule.type === 'atrule' && 'variable' in atRule && atRule.variable && !hasBlock(atRule)) ||
false
);
};
+44
View File
@@ -0,0 +1,44 @@
'use strict';
/** @typedef {import('postcss-value-parser').Node} ValueNode */
/**
* @param {ValueNode | undefined} valueNode
* @returns {boolean}
*/
module.exports = function (valueNode) {
if (!valueNode) {
return false;
}
if (valueNode.type !== 'function' || !valueNode.nodes || valueNode.value) {
return false;
}
// It's necessary to remove comments and spaces if they are present
const cleanNodes = valueNode.nodes.filter(
(node) => node.type !== 'comment' && node.type !== 'space',
);
// Map without comments and spaces will have the structure like $map (prop: value, prop2: value)
// ↑ ↑ ↑ ↑
// 0 1 2 3
if (cleanNodes[0] && cleanNodes[0].type !== 'word' && cleanNodes[0].type !== 'string') {
return false;
}
if (cleanNodes[1] && cleanNodes[1].value !== ':') {
return false;
}
// There is no need to check type or value of this node since it could be anything
if (!cleanNodes[2]) {
return false;
}
if (cleanNodes[3] && cleanNodes[3].value !== ',') {
return false;
}
return true;
};
+13
View File
@@ -0,0 +1,13 @@
'use strict';
const MATH_FUNCTIONS = require('../reference/mathFunctions');
/**
* Check whether a node is math function
*
* @param {import('postcss-value-parser').Node} node postcss-value-parser node
* @return {boolean} If `true`, the node is math function
*/
module.exports = function isMathFunction(node) {
return node.type === 'function' && MATH_FUNCTIONS.includes(node.value.toLowerCase());
};
+17
View File
@@ -0,0 +1,17 @@
'use strict';
// TODO: We need this polyfill because of the support of Node 10.
// When we will drop Node 10, please remove this polyfill.
// See <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger>
const isInteger =
Number.isInteger ||
function (value) {
return typeof value === 'number' && isFinite(value) && Math.floor(value) === value;
};
/**
* @param {unknown} value
*/
module.exports = function (value) {
return isInteger(value) && typeof value === 'number' && value >= 0;
};
+13
View File
@@ -0,0 +1,13 @@
'use strict';
/**
* Check whether it's a number or a number-like string:
* i.e. when coerced to a number it == itself.
*
* @param {string | number} value
*/
module.exports = function (value) {
/* eslint-disable eqeqeq */
return value.toString().trim().length !== 0 && Number(value) == value;
/* eslint-enable eqeqeq */
};
+22
View File
@@ -0,0 +1,22 @@
'use strict';
const isWhitespace = require('./isWhitespace');
/**
* Returns a Boolean indicating whether the the input string is only whitespace.
*
* @param {string} input
* @returns {boolean}
*/
module.exports = function (input) {
let isOnlyWhitespace = true;
for (let i = 0, l = input.length; i < l; i++) {
if (!isWhitespace(input[i])) {
isOnlyWhitespace = false;
break;
}
}
return isOnlyWhitespace;
};
+11
View File
@@ -0,0 +1,11 @@
'use strict';
/**
* Check whether a media feature is a range context one
*
* @param {string} mediaFeature feature
* @return {boolean} If `true`, media feature is a range context one
*/
module.exports = function (mediaFeature) {
return mediaFeature.includes('=') || mediaFeature.includes('<') || mediaFeature.includes('>');
};
+21
View File
@@ -0,0 +1,21 @@
'use strict';
/**
* Check whether a property is SCSS variable
*
* @param {string} property
* @returns {boolean}
*/
module.exports = function (property) {
// SCSS var (e.g. $var: x), list (e.g. $list: (x)) or map (e.g. $map: (key:value))
if (property.startsWith('$')) {
return true;
}
// SCSS var within a namespace (e.g. namespace.$var: x)
if (property.includes('.$')) {
return true;
}
return false;
};
+53
View File
@@ -0,0 +1,53 @@
'use strict';
const _ = require('lodash');
const getNextNonSharedLineCommentNode = require('./getNextNonSharedLineCommentNode');
const getPreviousNonSharedLineCommentNode = require('./getPreviousNonSharedLineCommentNode');
/** @typedef {import('postcss').Node} PostcssNode */
/**
*
* @param {PostcssNode | void} a
* @param {PostcssNode | void} b
*/
function nodesShareLines(a, b) {
return _.get(a, 'source.end.line') === _.get(b, 'source.start.line');
}
/**
* @param {PostcssNode} node
* @returns {boolean}
*/
module.exports = function isSharedLineComment(node) {
if (node.type !== 'comment') {
return false;
}
const previousNonSharedLineCommentNode = getPreviousNonSharedLineCommentNode(node);
if (nodesShareLines(previousNonSharedLineCommentNode, node)) {
return true;
}
const nextNonSharedLineCommentNode = getNextNonSharedLineCommentNode(node);
if (nextNonSharedLineCommentNode && nodesShareLines(node, nextNonSharedLineCommentNode)) {
return true;
}
const parentNode = node.parent;
// It's a first child and located on the same line as block start
if (
parentNode !== undefined &&
parentNode.type !== 'root' &&
parentNode.index(node) === 0 &&
node.raws.before !== undefined &&
!node.raws.before.includes('\n')
) {
return true;
}
return false;
};
+12
View File
@@ -0,0 +1,12 @@
'use strict';
/**
* Check if a string is a single line (i.e. does not contain
* any newline characters).
*
* @param {string} input
* @return {boolean}
*/
module.exports = function (input) {
return !/[\n\r]/.test(input);
};
+29
View File
@@ -0,0 +1,29 @@
'use strict';
/**
* Check whether a at-rule is standard
*
* @param {import('postcss').AtRule | import('postcss-less').AtRule} atRule postcss at-rule node
* @return {boolean} If `true`, the declaration is standard
*/
module.exports = function (atRule) {
// Ignore scss `@content` inside mixins
if (!atRule.nodes && atRule.params === '') {
return false;
}
// Ignore Less mixins
if ('mixin' in atRule && atRule.mixin) {
return false;
}
// Ignore Less detached ruleset `@detached-ruleset: { background: red; }; .top { @detached-ruleset(); }`
if (
('variable' in atRule && atRule.variable) ||
(!atRule.nodes && atRule.raws.afterName === '' && atRule.params[0] === '(')
) {
return false;
}
return true;
};
+34
View File
@@ -0,0 +1,34 @@
'use strict';
/**
* Check whether a combinator is standard
*
* @param {import('postcss-selector-parser').Combinator} node postcss-selector-parser node (of type combinator)
* @return {boolean} If `true`, the combinator is standard
*/
module.exports = function (node) {
// if it's not a combinator, then it's not a standard combinator
if (node.type !== 'combinator') {
return false;
}
// Ignore reference combinators like `/deep/`
if (node.value.startsWith('/') || node.value.endsWith('/')) {
return false;
}
// ignore the combinators that are the first or last node in their container
if (node.parent !== undefined && node.parent !== null) {
const parent = node.parent;
if (node === parent.first) {
return false;
}
if (node === parent.last) {
return false;
}
}
return true;
};
+61
View File
@@ -0,0 +1,61 @@
'use strict';
const isScssVariable = require('./isScssVariable');
const { isRoot } = require('./typeGuards');
/**
* @param {string} [lang]
*/
function isStandardSyntaxLang(lang) {
return lang && (lang === 'css' || lang === 'custom-template' || lang === 'template-literal');
}
/**
* Check whether a declaration is standard
*
* @param {import('postcss').Declaration | import('postcss-less').Declaration} decl
*/
module.exports = function (decl) {
const prop = decl.prop;
const parent = decl.parent;
// Declarations belong in a declaration block or standard CSS source
if (
isRoot(parent) &&
parent.source &&
!isStandardSyntaxLang(
/** @type {import('postcss').NodeSource & {lang?: string}} */ (parent.source).lang,
)
) {
return false;
}
// SCSS var
if (isScssVariable(prop)) {
return false;
}
// Less var (e.g. @var: x), but exclude variable interpolation (e.g. @{var})
if (prop[0] === '@' && prop[1] !== '{') {
return false;
}
// Sass nested properties (e.g. border: { style: solid; color: red; })
if (
// @ts-ignore TODO TYPES selector does not exists
parent.selector &&
// @ts-ignore
parent.selector[parent.selector.length - 1] === ':' &&
// @ts-ignore
parent.selector.substring(0, 2) !== '--'
) {
return false;
}
// Less &:extend
if ('extend' in decl && decl.extend) {
return false;
}
return true;
};
+16
View File
@@ -0,0 +1,16 @@
'use strict';
/**
* Check whether a function is standard
*
* @param {import('postcss-value-parser').Node} node
* @returns {boolean}
*/
module.exports = function (node) {
// Function nodes without names are things in parentheses like Sass lists
if (!node.value) {
return false;
}
return true;
};
+21
View File
@@ -0,0 +1,21 @@
'use strict';
/**
* Check whether a math function is standard
*
* @param {string} mathFunction
* @returns {boolean}
*/
module.exports = function isStandardSyntaxMathFunction(mathFunction) {
// SCSS variable
if (mathFunction.includes('$')) {
return false;
}
// Less variable
if (mathFunction.includes('@')) {
return false;
}
return true;
};
+26
View File
@@ -0,0 +1,26 @@
'use strict';
const hasInterpolation = require('../utils/hasInterpolation');
/**
* Check whether a media feature is standard
*
* @param {string} mediaFeature
* @returns {boolean}
*/
module.exports = function (mediaFeature) {
// Remove outside parens
mediaFeature = mediaFeature.slice(1, -1);
// Parentheticals used for non-standard operations e.g. ($var - 10)
if (mediaFeature.includes('(')) {
return false;
}
// SCSS or Less interpolation
if (hasInterpolation(mediaFeature)) {
return false;
}
return true;
};
+16
View File
@@ -0,0 +1,16 @@
'use strict';
/**
* Check whether a media feature name is standard
*
* @param {string} mediaFeatureName
* @returns {boolean}
*/
module.exports = function (mediaFeatureName) {
// SCSS interpolation
if (/#{.+?}|\$.+?/.test(mediaFeatureName)) {
return false;
}
return true;
};
+34
View File
@@ -0,0 +1,34 @@
'use strict';
const hasInterpolation = require('../utils/hasInterpolation');
const isScssVariable = require('./isScssVariable');
/**
* Check whether a property is standard
*
* @param {string} property
* @returns {boolean}
*/
module.exports = function (property) {
// SCSS var
if (isScssVariable(property)) {
return false;
}
// Less var (e.g. @var: x)
if (property.startsWith('@')) {
return false;
}
// Less append property value with space (e.g. transform+_: scale(2))
if (property.endsWith('+') || property.endsWith('+_')) {
return false;
}
// SCSS or Less interpolation
if (hasInterpolation(property)) {
return false;
}
return true;
};
+69
View File
@@ -0,0 +1,69 @@
'use strict';
const _ = require('lodash');
const isCustomPropertySet = require('../utils/isCustomPropertySet');
const isStandardSyntaxSelector = require('../utils/isStandardSyntaxSelector');
/**
* Check whether a Node is a standard rule
*
* @param {import('postcss').Rule | import('postcss-less').Rule} rule
* @returns {boolean}
*/
module.exports = function (rule) {
if (rule.type !== 'rule') {
return false;
}
// Get full selector
const selector = _.get(rule, 'raws.selector.raw', rule.selector);
if (!isStandardSyntaxSelector(rule.selector)) {
return false;
}
// Custom property set (e.g. --custom-property-set: {})
if (isCustomPropertySet(rule)) {
return false;
}
// Called Less mixin (e.g. a { .mixin() })
// @ts-ignore TODO TYPES support LESS and SASS types somehow
if (rule.mixin) {
return false;
}
// Less detached rulesets
if (selector.startsWith('@') && selector.endsWith(':')) {
return false;
}
// Ignore Less &:extend rule
if ('extend' in rule && rule.extend) {
return false;
}
// Ignore mixin or &:extend rule
// https://github.com/shellscape/postcss-less/blob/master/lib/less-parser.js#L52
// @ts-ignore TODO TYPES support LESS and SASS types somehow
if (rule.params && rule.params[0]) {
return false;
}
// Non-outputting Less mixin definition (e.g. .mixin() {})
if (selector.endsWith(')') && !selector.includes(':')) {
return false;
}
// Less guards
if (/when\s+(not\s+)*\(/.test(selector)) {
return false;
}
// Ignore Scss nested properties
if (selector.endsWith(':')) {
return false;
}
return true;
};
+38
View File
@@ -0,0 +1,38 @@
'use strict';
const hasInterpolation = require('../utils/hasInterpolation');
/**
* Check whether a selector is standard
*
* @param {string} selector
* @returns {boolean}
*/
module.exports = function (selector) {
// SCSS or Less interpolation
if (hasInterpolation(selector)) {
return false;
}
// SCSS placeholder selectors
if (selector.startsWith('%')) {
return false;
}
// Less :extend()
if (/:extend(\(.*?\))?/.test(selector)) {
return false;
}
// Less mixin with resolved nested selectors (e.g. .foo().bar or .foo(@a, @b)[bar])
if (/\.[\w-]+\(.*\).+/i.test(selector)) {
return false;
}
// ERB template tags
if (selector.includes('<%') || selector.includes('%>')) {
return false;
}
return true;
};
+53
View File
@@ -0,0 +1,53 @@
'use strict';
const keywordSets = require('../reference/keywordSets');
/**
* Check whether a type selector is standard
*
* @param {import('postcss-selector-parser').Tag} node postcss-selector-parser node (of type tag)
* @return {boolean} If `true`, the type selector is standard
*/
module.exports = function (node) {
// postcss-selector-parser includes the arguments to nth-child() functions
// as "tags", so we need to ignore them ourselves.
// The fake-tag's "parent" is actually a selector node, whose parent
// should be the :nth-child pseudo node.
if (!node.parent || !node.parent.parent) {
return false;
}
const _node$parent$parent = node.parent.parent;
const parentType = _node$parent$parent.type;
const parentValue = _node$parent$parent.value;
if (parentValue) {
const normalisedParentName = parentValue.toLowerCase().replace(/:+/, '');
if (
parentType === 'pseudo' &&
(keywordSets.aNPlusBNotationPseudoClasses.has(normalisedParentName) ||
keywordSets.aNPlusBOfSNotationPseudoClasses.has(normalisedParentName) ||
keywordSets.linguisticPseudoClasses.has(normalisedParentName) ||
keywordSets.shadowTreePseudoElements.has(normalisedParentName))
) {
return false;
}
}
// &-bar is a nesting selector combined with a suffix
if (node.prev() && node.prev().type === 'nesting') {
return false;
}
if (node.value.startsWith('%')) {
return false;
}
// Reference combinators like `/deep/`
if (node.value.startsWith('/') && node.value.endsWith('/')) {
return false;
}
return true;
};
+48
View File
@@ -0,0 +1,48 @@
'use strict';
const hasLessInterpolation = require('../utils/hasLessInterpolation');
const hasPsvInterpolation = require('../utils/hasPsvInterpolation');
const hasScssInterpolation = require('../utils/hasScssInterpolation');
const hasTplInterpolation = require('../utils/hasTplInterpolation');
/**
* Check whether a URL is standard
*
* @param {string} url
* @returns {boolean}
*/
module.exports = function (url) {
if (url.length === 0) {
return true;
}
// Sass interpolation works anywhere
if (hasScssInterpolation(url) || hasTplInterpolation(url) || hasPsvInterpolation(url)) {
return false;
}
// Inside `'` and `"` work only LESS interpolation
if ((url.startsWith(`'`) && url.endsWith(`'`)) || (url.startsWith(`"`) && url.endsWith(`"`))) {
if (hasLessInterpolation(url)) {
return false;
}
return true;
}
// Less variable works only at the beginning
// Check is less variable, allow use '@url/some/path'
// https://github.com/less/less.js/blob/3.x/lib/less/parser/parser.js#L547
if (url.startsWith('@') && /^@@?[\w-]+$/.test(url)) {
return false;
}
// In url without quotes scss variable can be everywhere
// But in this case it is allowed to use only specific characters
// Also forbidden "/" at the end of url
if (url.includes('$') && /^[$\s\w+-/*'"/]+$/.test(url) && !url.endsWith('/')) {
return false;
}
return true;
};
+47
View File
@@ -0,0 +1,47 @@
'use strict';
const hasInterpolation = require('../utils/hasInterpolation');
/**
* Check whether a value is standard
*
* @param {string} value
* @returns {boolean}
*/
module.exports = function (value) {
let normalizedValue = value;
// Ignore operators before variables (example -$variable)
if (/^[-+*/]/.test(value[0])) {
normalizedValue = normalizedValue.slice(1);
}
// SCSS variable (example $variable)
if (normalizedValue.startsWith('$')) {
return false;
}
// SCSS namespace (example namespace.$variable)
if (/^.+\.\$/.test(value)) {
return false;
}
// Less variable
if (normalizedValue.startsWith('@')) {
return false;
}
// SCSS or Less interpolation
if (hasInterpolation(normalizedValue)) {
return false;
}
// WebExtension replacement keyword used by Chrome/Firefox
// more information: https://developer.chrome.com/extensions/i18n
// and https://github.com/stylelint/stylelint/issues/4707
if (/__MSG_\S+__/.test(value)) {
return false;
}
return true;
};
+38
View File
@@ -0,0 +1,38 @@
'use strict';
const keywordSets = require('../reference/keywordSets');
const valueParser = require('postcss-value-parser');
/**
* Check if a word is a font-size value.
*
* @param {string} word
* @returns {boolean}
*/
module.exports = function (word) {
if (!word) {
return false;
}
if (keywordSets.fontSizeKeywords.has(word)) {
return true;
}
const numberUnit = valueParser.unit(word);
if (!numberUnit) {
return false;
}
const unit = numberUnit.unit;
if (unit === '%') {
return true;
}
if (keywordSets.lengthUnits.has(unit.toLowerCase())) {
return true;
}
return false;
};
+11
View File
@@ -0,0 +1,11 @@
'use strict';
/**
* Check if a value is a valid 3, 4, 6 or 8 digit hex
*
* @param {string} value
* @returns {boolean}
*/
module.exports = function (value) {
return /^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value);
};
+11
View File
@@ -0,0 +1,11 @@
'use strict';
/**
* Check whether a word is a variable i.e var(--custom-property).
*
* @param {string} word
* @returns {boolean}
*/
module.exports = function (word) {
return word.toLowerCase().startsWith('var(');
};
+11
View File
@@ -0,0 +1,11 @@
'use strict';
/**
* Check if a character is whitespace.
*
* @param {string} char
* @returns {boolean}
*/
module.exports = function (char) {
return [' ', '\n', '\t', '\r', '\f'].includes(char);
};
+85
View File
@@ -0,0 +1,85 @@
'use strict';
/**
* Compares a string to a second value that, if it fits a certain convention,
* is converted to a regular expression before the comparison.
* If it doesn't fit the convention, then two strings are compared.
*
* Any strings starting and ending with `/` are interpreted
* as regular expressions.
*
* @param {string} input
* @param {string | RegExp | Array<string | RegExp>} comparison
*
* @returns {false | {match: string, pattern: (string | RegExp) }}
*/
module.exports = function matchesStringOrRegExp(input, comparison) {
if (!Array.isArray(input)) {
return testAgainstStringOrRegExpOrArray(input, comparison);
}
for (const inputItem of input) {
const testResult = testAgainstStringOrRegExpOrArray(inputItem, comparison);
if (testResult) {
return testResult;
}
}
return false;
};
/**
* @param {string} value
* @param {string | RegExp | Array<string | RegExp>} comparison
*/
function testAgainstStringOrRegExpOrArray(value, comparison) {
if (!Array.isArray(comparison)) {
return testAgainstStringOrRegExp(value, comparison);
}
for (const comparisonItem of comparison) {
const testResult = testAgainstStringOrRegExp(value, comparisonItem);
if (testResult) {
return testResult;
}
}
return false;
}
/**
* @param {string} value
* @param {string | RegExp} comparison
*/
function testAgainstStringOrRegExp(value, comparison) {
// If it's a RegExp, test directly
if (comparison instanceof RegExp) {
return comparison.test(value) ? { match: value, pattern: comparison } : false;
}
// Check if it's RegExp in a string
const firstComparisonChar = comparison[0];
const lastComparisonChar = comparison[comparison.length - 1];
const secondToLastComparisonChar = comparison[comparison.length - 2];
const comparisonIsRegex =
firstComparisonChar === '/' &&
(lastComparisonChar === '/' ||
(secondToLastComparisonChar === '/' && lastComparisonChar === 'i'));
const hasCaseInsensitiveFlag = comparisonIsRegex && lastComparisonChar === 'i';
// If so, create a new RegExp from it
if (comparisonIsRegex) {
const valueMatches = hasCaseInsensitiveFlag
? new RegExp(comparison.slice(1, -2), 'i').test(value)
: new RegExp(comparison.slice(1, -1)).test(value);
return valueMatches ? { match: value, pattern: comparison } : false;
}
// Otherwise, it's a string. Do a strict comparison
return value === comparison ? { match: value, pattern: comparison } : false;
}
+20
View File
@@ -0,0 +1,20 @@
'use strict';
/** @typedef {import('postcss').Node} PostcssNode */
/**
* Get the next non-comment node in a PostCSS AST
* at or after a given node.
*
* @param {PostcssNode | void} startNode
* @returns {PostcssNode | null}
*/
module.exports = function nextNonCommentNode(startNode) {
if (!startNode || !startNode.next) return null;
if (startNode.type === 'comment') {
return nextNonCommentNode(startNode.next());
}
return startNode;
};
+20
View File
@@ -0,0 +1,20 @@
'use strict';
class NoFilesFoundError extends Error {
/**
* @param {string|string[]} fileList
*/
constructor(fileList) {
super();
if (typeof fileList === 'string') {
fileList = [fileList];
}
const pattern = fileList.filter((i) => !i.startsWith('!')).join(', ');
this.message = `No files matching the pattern "${pattern}" were found.`;
}
}
module.exports = NoFilesFoundError;
+43
View File
@@ -0,0 +1,43 @@
'use strict';
/**
* Create a collection of Maps that serve to contextualize a given node.
* This is useful to ensure that you only compare nodes that share a certain
* context.
*
* All nodes are initially contextualized by their input source.
* From there, you can contextualize them however you want.
*
* For a usage example, see `selector-no-descending-specificity`.
*/
module.exports = function () {
const contextMap = new Map();
return {
/**
* @param {import('postcss').Node} node
*/
getContext(node, /** @type {any[]} */ ...subContexts) {
// TODO TYPES node.source possible undefined
const nodeSource = /** @type {import('postcss').NodeSource} */ (node.source).input.from;
const baseContext = creativeGetMap(contextMap, nodeSource);
return subContexts.reduce((result, context) => {
return creativeGetMap(result, context);
}, baseContext);
},
};
};
/**
* TODO TYPES
* @param {Map<any, any>} someMap
* @param {any} someThing
*/
function creativeGetMap(someMap, someThing) {
if (!someMap.has(someThing)) {
someMap.set(someThing, new Map());
}
return someMap.get(someThing);
}
+22
View File
@@ -0,0 +1,22 @@
'use strict';
const matchesStringOrRegExp = require('./matchesStringOrRegExp');
/**
* Check if an options object's propertyName contains a user-defined string or
* regex that matches the passed in input.
*
* @param {{ [x: string]: any; }} options
* @param {string} propertyName
* @param {string} input
*
* @returns {boolean}
*/
module.exports = function optionsMatches(options, propertyName, input) {
return Boolean(
options &&
options[propertyName] &&
typeof input === 'string' &&
matchesStringOrRegExp(input, options[propertyName]),
);
};
+15
View File
@@ -0,0 +1,15 @@
'use strict';
// To generate parser.js run the following command in the current directory:
//
// npx jison-gho parser.jison -o parser.js
const parse = require('./parser').parse;
/**
* @param {string} exp
* @returns {object}
*/
module.exports = function parseCalcExpression(exp) {
return parse(exp);
};
+172
View File
@@ -0,0 +1,172 @@
/* description: Parses expressions. */
%options case-insensitive
/* lexical grammar */
%lex
%%
\s+ /* skip whitespace */
(\-(webkit|moz)\-)?calc\b return 'CALC';
[a-z][a-z0-9-]*\s*\((?:(?:\"(?:\\.|[^\"\\])*\"|\'(?:\\.|[^\'\\])*\')|\([^)]*\)|[^\(\)]*)*\) return 'FUNCTION';
"*" return 'MUL';
"/" return 'DIV';
"+" return 'ADD';
"-" return 'SUB';
([0-9]+("."[0-9]+)?|"."[0-9]+)em\b return 'LENGTH'; // em
([0-9]+("."[0-9]+)?|"."[0-9]+)ex\b return 'LENGTH'; // ex
([0-9]+("."[0-9]+)?|"."[0-9]+)ch\b return 'LENGTH'; // ch
([0-9]+("."[0-9]+)?|"."[0-9]+)rem\b return 'LENGTH'; // rem
([0-9]+("."[0-9]+)?|"."[0-9]+)vw\b return 'LENGTH'; // vw
([0-9]+("."[0-9]+)?|"."[0-9]+)vh\b return 'LENGTH'; // vh
([0-9]+("."[0-9]+)?|"."[0-9]+)vmin\b return 'LENGTH'; // vmin
([0-9]+("."[0-9]+)?|"."[0-9]+)vmax\b return 'LENGTH'; // vmax
([0-9]+("."[0-9]+)?|"."[0-9]+)vm\b return 'LENGTH'; // vm (non-standard name)
([0-9]+("."[0-9]+)?|"."[0-9]+)px\b return 'LENGTH'; // px
([0-9]+("."[0-9]+)?|"."[0-9]+)mm\b return 'LENGTH'; // mm
([0-9]+("."[0-9]+)?|"."[0-9]+)cm\b return 'LENGTH'; // cm
([0-9]+("."[0-9]+)?|"."[0-9]+)in\b return 'LENGTH'; // in
([0-9]+("."[0-9]+)?|"."[0-9]+)pt\b return 'LENGTH'; // pt
([0-9]+("."[0-9]+)?|"."[0-9]+)pc\b return 'LENGTH'; // pc
([0-9]+("."[0-9]+)?|"."[0-9]+)Q\b return 'LENGTH'; // Q
([0-9]+("."[0-9]+)?|"."[0-9]+)fr\b return 'LENGTH'; // fr
([0-9]+("."[0-9]+)?|"."[0-9]+)deg\b return 'ANGLE'; // deg
([0-9]+("."[0-9]+)?|"."[0-9]+)grad\b return 'ANGLE'; // grad
([0-9]+("."[0-9]+)?|"."[0-9]+)turn\b return 'ANGLE'; // turn
([0-9]+("."[0-9]+)?|"."[0-9]+)rad\b return 'ANGLE'; // rad
([0-9]+("."[0-9]+)?|"."[0-9]+)s\b return 'TIME'; // s
([0-9]+("."[0-9]+)?|"."[0-9]+)ms\b return 'TIME'; // ms
([0-9]+("."[0-9]+)?|"."[0-9]+)Hz\b return 'FREQ'; // Hz
([0-9]+("."[0-9]+)?|"."[0-9]+)kHz\b return 'FREQ'; // kHz
([0-9]+("."[0-9]+)?|"."[0-9]+)dpi\b return 'RES'; // dpi
([0-9]+("."[0-9]+)?|"."[0-9]+)dpcm\b return 'RES'; // dpcm
([0-9]+("."[0-9]+)?|"."[0-9]+)dppx\b return 'RES'; // dppm
([0-9]+("."[0-9]+)?|"."[0-9]+)\% return 'PERCENTAGE';
([0-9]+("."[0-9]+)?|"."[0-9]+)\b return 'NUMBER';
"(" return 'LPAREN';
")" return 'RPAREN';
#\{([\s\S]*?)\} return 'UNKNOWN'; // scss interpolation
@\{([\s\S]*?)\} return 'UNKNOWN'; // less interpolation
\S[^\s()*/+-]* return 'UNKNOWN';
<<EOF>> return 'EOF';
/lex
%left ADD SUB
%left MUL DIV
%left UPREC
%start expression
%%
expression
: math_expression EOF { return $1; }
;
math_expression
: CALC LPAREN math_expression RPAREN {
$$ = $3;
$$.source.start = { index: @1.range[0] };
$$.source.end = { index: @4.range[1] };
}
| math_expression ADD math_expression {
$$ = {
type: 'MathExpression', operator: $2, left: $1, right: $3,
source: {
start: $1.source.start, end: $3.source.end,
operator: { start: { index: @2.range[0] }, end: { index: @2.range[1] } }
}
};
}
| math_expression SUB math_expression {
$$ = {
type: 'MathExpression', operator: $2, left: $1, right: $3,
source: {
start: $1.source.start, end: $3.source.end,
operator: { start: { index: @2.range[0] }, end: { index: @2.range[1] } }
}
};
}
| math_expression MUL math_expression {
$$ = {
type: 'MathExpression', operator: $2, left: $1, right: $3,
source: {
start: $1.source.start, end: $3.source.end,
operator: { start: { index: @2.range[0] }, end: { index: @2.range[1] } }
}
};
}
| math_expression DIV math_expression {
$$ = {
type: 'MathExpression', operator: $2, left: $1, right: $3,
source: {
start: $1.source.start, end: $3.source.end,
operator: { start: { index: @2.range[0] }, end: { index: @2.range[1] } }
}
};
}
| SUB math_expression %prec UPREC {
if (@1.range[1] !== $2.source.start.index) {
throw new Error('Unexpected spaces was found between sign and value');
}
if (typeof $2.value !== 'number') {
throw new Error('Unexpected sign');
}
if ($2.sign) {
throw new Error('Unexpected continuous sign');
}
$$ = $2;
$$.sign = '-'
$$.value = -$2.value;
$$.source.start.index = @1.range[0];
}
| ADD math_expression %prec UPREC {
if (@1.range[1] !== $2.source.start.index) {
throw new Error('Unexpected spaces was found between sign and value');
}
if (typeof $2.value !== 'number') {
throw new Error('Unexpected sign');
}
if ($2.sign) {
throw new Error('Unexpected continuous sign');
}
$$ = $2;
$$.sign = '+'
$$.source.start.index = @1.range[0];
}
| LPAREN math_expression RPAREN {
$$ = $2;
$$.source.start = { index: @1.range[0] };
$$.source.end = { index: @3.range[1] };
}
| function { $$ = $1; }
| css_value { $$ = $1; }
| value { $$ = $1; }
;
value
: NUMBER { $$ = { type: 'Value', value: parseFloat($1), source: { start: { index: @1.range[0] }, end: { index: @1.range[1] } } }; }
;
function
: FUNCTION { $$ = { type: 'Function', value: $1, source: { start: { index: @1.range[0] }, end: { index: @1.range[1] } } }; }
;
css_value
: LENGTH { $$ = { type: 'LengthValue', value: parseFloat($1), unit: /[a-z]+/i.exec($1)[0], source: { start: { index: @1.range[0] }, end: { index: @1.range[1] } } }; }
| ANGLE { $$ = { type: 'AngleValue', value: parseFloat($1), unit: /[a-z]+/i.exec($1)[0], source: { start: { index: @1.range[0] }, end: { index: @1.range[1] } } }; }
| TIME { $$ = { type: 'TimeValue', value: parseFloat($1), unit: /[a-z]+/i.exec($1)[0], source: { start: { index: @1.range[0] }, end: { index: @1.range[1] } } }; }
| FREQ { $$ = { type: 'FrequencyValue', value: parseFloat($1), unit: /[a-z]+/i.exec($1)[0], source: { start: { index: @1.range[0] }, end: { index: @1.range[1] } } }; }
| RES { $$ = { type: 'ResolutionValue', value: parseFloat($1), unit: /[a-z]+/i.exec($1)[0], source: { start: { index: @1.range[0] }, end: { index: @1.range[1] } } }; }
| PERCENTAGE { $$ = { type: 'PercentageValue', value: parseFloat($1), unit: '%', source: { start: { index: @1.range[0] }, end: { index: @1.range[1] } } }; }
| UNKNOWN { $$ = { type: 'UnknownValue', value: $1, unit: '', source: { start: { index: @1.range[0] }, end: { index: @1.range[1] } } }; }
;
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
'use strict';
const selectorParser = require('postcss-selector-parser');
/**
* @param {string} selector
* @param {import('stylelint').PostcssResult} result
* @param {import('postcss').Node} node
* @param {Function} cb
*/
module.exports = function parseSelector(selector, result, node, cb) {
try {
// @ts-ignore TODO TYPES wrong postcss-selector-parser types
return selectorParser(cb).processSync(selector);
} catch {
result.warn('Cannot parse selector', { node, stylelintType: 'parseError' });
}
};
+22
View File
@@ -0,0 +1,22 @@
'use strict';
/**
* If `map` already has the given `key`, returns its value. Otherwise, calls
* `callback`, adds the result to `map` at `key`, and then returns it.
*
* @template K
* @template V
* @param {Map<K, V>} map
* @param {K} key
* @param {() => V} callback
* @returns {V}
*/
module.exports = function (map, key, callback) {
if (map.has(key)) return /** @type {V} */ (map.get(key));
const value = callback();
map.set(key, value);
return value;
};
+20
View File
@@ -0,0 +1,20 @@
'use strict';
/**
* Stringify PostCSS node including its raw "before" string.
*
* @param {import('postcss').Node} node
*
* @returns {string}
*/
module.exports = function (node) {
let result = '';
if (node.raws.before) {
result += node.raws.before;
}
result += node.toString();
return result;
};
+15
View File
@@ -0,0 +1,15 @@
'use strict';
/**
* Remove empty lines before a node. Mutates the node.
*
* @param {import('postcss').Node} node
* @param {'\n' | '\r\n'} newline
*/
function removeEmptyLinesAfter(node, newline) {
node.raws.after = node.raws.after ? node.raws.after.replace(/(\r?\n\s*\r?\n)+/g, newline) : '';
return node;
}
module.exports = removeEmptyLinesAfter;
+15
View File
@@ -0,0 +1,15 @@
'use strict';
/**
* Remove empty lines before a node. Mutates the node.
*
* @param {import('postcss').Node} node
* @param {'\n' | '\r\n'} newline
*/
function removeEmptyLinesBefore(node, newline) {
node.raws.before = node.raws.before ? node.raws.before.replace(/(\r?\n\s*\r?\n)+/g, newline) : '';
return node;
}
module.exports = removeEmptyLinesBefore;

Some files were not shown because too many files have changed in this diff Show More