Skip to content

Commit

Permalink
Move stylelint utils into project
Browse files Browse the repository at this point in the history
  • Loading branch information
Marieke-C committed Mar 8, 2024
1 parent 0e6557b commit bfae0dd
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 8 deletions.
11 changes: 3 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,9 @@ import _ from "lodash";
import stylelint from "stylelint";
import selectorParser from 'postcss-selector-parser';

import hasInterpolation from 'stylelint/lib/utils/hasInterpolation.mjs';
import hasLessInterpolation from 'stylelint/lib/utils/hasLessInterpolation.mjs';
import hasPsvInterpolation from "stylelint/lib/utils/hasPsvInterpolation.mjs";
import hasScssInterpolation from "stylelint/lib/utils/hasScssInterpolation.mjs";
import hasTplInterpolation from "stylelint/lib/utils/hasTplInterpolation.mjs";
import isStandardSyntaxRule from 'stylelint/lib/utils/isStandardSyntaxRule.mjs';
import matchesStringOrRegExp from 'stylelint/lib/utils/matchesStringOrRegExp.mjs';
import isStandardSyntaxSelector from 'stylelint/lib/utils/isStandardSyntaxSelector.mjs';
import isStandardSyntaxRule from "./utils/isStandardSyntaxRule.mjs";
import matchesStringOrRegExp from "./utils/matchesStringOrRegExp.mjs";
import isStandardSyntaxSelector from "./utils/isStandardSyntaxSelector.mjs";

const {
createPlugin,
Expand Down
24 changes: 24 additions & 0 deletions utils/hasInterpolation.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import hasLessInterpolation from './hasLessInterpolation.mjs';
import hasPsvInterpolation from './hasPsvInterpolation.mjs';
import hasScssInterpolation from './hasScssInterpolation.mjs';
import hasTplInterpolation from './hasTplInterpolation.mjs';

/**
* Check whether a string has interpolation
*
* @param {string} string
* @return {boolean} If `true`, a string has interpolation
*/
export default function hasInterpolation(string) {
// SCSS or Less interpolation
if (
hasLessInterpolation(string) ||
hasScssInterpolation(string) ||
hasTplInterpolation(string) ||
hasPsvInterpolation(string)
) {
return true;
}

return false;
}
11 changes: 11 additions & 0 deletions utils/hasLessInterpolation.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const HAS_LESS_INTERPOLATION = /@\{.+?\}/;

/**
* Check whether a string has less interpolation
*
* @param {string} string
* @return {boolean} If `true`, a string has less interpolation
*/
export default function hasLessInterpolation(string) {
return HAS_LESS_INTERPOLATION.test(string);
}
11 changes: 11 additions & 0 deletions utils/hasPsvInterpolation.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const HAS_PSV_INTERPOLATION = /\$\(.+?\)/;

/**
* Check whether a string has postcss-simple-vars interpolation
*
* @param {string} string
* @returns {boolean}
*/
export default function hasPsvInterpolation(string) {
return HAS_PSV_INTERPOLATION.test(string);
}
11 changes: 11 additions & 0 deletions utils/hasScssInterpolation.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const HAS_SCSS_INTERPOLATION = /#\{.+?\}/s;

/**
* Check whether a string has scss interpolation
*
* @param {string} string
* @returns {boolean}
*/
export default function hasScssInterpolation(string) {
return HAS_SCSS_INTERPOLATION.test(string);
}
11 changes: 11 additions & 0 deletions utils/hasTplInterpolation.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const HAS_TPL_INTERPOLATION = /\{.+?\}/s;

/**
* 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
*/
export default function hasTplInterpolation(string) {
return HAS_TPL_INTERPOLATION.test(string);
}
24 changes: 24 additions & 0 deletions utils/isStandardSyntaxRule.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import isStandardSyntaxSelector from './isStandardSyntaxSelector.mjs';

/**
* Check whether a Node is a standard rule
*
* @param {import('postcss').Rule | import('postcss-less').Rule} rule
* @returns {boolean}
*/
export default function isStandardSyntaxRule(rule) {
if (rule.type !== 'rule') {
return false;
}

// Ignore Less &:extend rule
if ('extend' in rule && rule.extend) {
return false;
}

if (!isStandardSyntaxSelector(rule.selector)) {
return false;
}

return true;
}
56 changes: 56 additions & 0 deletions utils/isStandardSyntaxSelector.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import hasInterpolation from './hasInterpolation.mjs';

/**
* Check whether a selector is standard
*
* @param {string} selector
* @returns {boolean}
*/
export default function isStandardSyntaxSelector(selector) {
// SCSS or Less interpolation
if (hasInterpolation(selector)) {
return false;
}

// SCSS placeholder selectors
if (selector.startsWith('%')) {
return false;
}

// SCSS nested properties
if (selector.endsWith(':')) {
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-]+\(.*\).+/.test(selector)) {
return false;
}

// Less non-outputting mixin definition (e.g. .mixin() {})
if (selector.endsWith(')') && !selector.includes(':')) {
return false;
}

// Less Parametric mixins (e.g. .mixin(@variable: x) {})
if (/\(@.*\)$/.test(selector)) {
return false;
}

// ERB template tags
if (selector.includes('<%') || selector.includes('%>')) {
return false;
}

// SCSS and Less comments
if (selector.includes('//')) {
return false;
}

return true;
}
87 changes: 87 additions & 0 deletions utils/matchesStringOrRegExp.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* 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 | Array<string>} input
* @param {string | RegExp | Array<string | RegExp>} comparison
*
* @returns {false | {match: string, pattern: (string | RegExp), substring: string}}
*/
export default 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) {
const match = value.match(comparison);

return match ? { match: value, pattern: comparison, substring: match[0] || '' } : 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 valueMatch = hasCaseInsensitiveFlag
? value.match(new RegExp(comparison.slice(1, -2), 'i'))
: value.match(new RegExp(comparison.slice(1, -1)));

return valueMatch
? { match: value, pattern: comparison, substring: valueMatch[0] || '' }
: false;
}

// Otherwise, it's a string. Do a strict comparison
return value === comparison ? { match: value, pattern: comparison, substring: value } : false;
}

0 comments on commit bfae0dd

Please sign in to comment.