Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add eslint-plugin-regexp #141

Closed
ybiquitous opened this issue Sep 5, 2021 · 9 comments · Fixed by #142
Closed

Add eslint-plugin-regexp #141

ybiquitous opened this issue Sep 5, 2021 · 9 comments · Fixed by #142
Assignees
Labels
status: ask to implement ask before implementing as may no longer be relevant

Comments

@ybiquitous
Copy link
Member

ybiquitous commented Sep 5, 2021

What is the problem you're trying to solve?

I'd like to reduce bugs related to a regex and make it safer to write a regex.

What solution would you like to see?

I suggest adding the eslint-plugin-regexp plugin which is a work by @ota-meshi (a member of our team).
The plugin is for "finding RegExp mistakes and RegExp style guide violations."

Demo

I tried the plugin on the v14 branch of the stylelint/stylelint repository. (HEAD: c1713eac)

  1. Install the plugin via npm i -D eslint-plugin-regexp
  2. Add "plugin:regexp/recommended" to eslintConfig.extends in package.json
  3. Run npm run lint:js -- -f codeframe

Result:

> stylelint@13.13.1 lint:js
> eslint . --cache --max-warnings=0 "-f" "codeframe"

error: Capturing group number 1 is defined but never used (regexp/no-unused-capturing-group) at lib/__tests__/standalone-syntax.test.js:23:39:
  21 | 		expect(results).toHaveLength(6);
  22 | 
> 23 | 		const safeParserExtensionsTest = /\.(css|pcss|postcss)$/i;
     | 		                                    ^
  24 | 
  25 | 		results
  26 | 			.filter((result) => !safeParserExtensionsTest.test(result.source))


error: Capturing group number 1 is defined but never used (regexp/no-unused-capturing-group) at lib/rules/color-hex-alpha/index.js:16:16:
  14 | });
  15 | 
> 16 | const HEX = /^#([\da-f]{3,4}|[\da-f]{6}|[\da-f]{8})$/i;
     |                ^
  17 | 
  18 | /** @type {import('stylelint').StylelintRule} */
  19 | const rule = (primary) => {


error: Unexpected character class range '0-9'. Use '\d' instead (regexp/prefer-d) at lib/rules/color-hex-case/index.js:18:17:
  16 | });
  17 | 
> 18 | const HEX = /^#[0-9A-Za-z]+/;
     |                 ^
  19 | const IGNORED_FUNCTIONS = new Set(['url']);
  20 | 
  21 | /** @type {import('stylelint').StylelintRule} */


error: Unexpected character class range '0-9'. Use '\d' instead (regexp/prefer-d) at lib/rules/color-hex-length/index.js:18:17:
  16 | });
  17 | 
> 18 | const HEX = /^#[0-9A-Za-z]+/;
     |                 ^
  19 | const IGNORED_FUNCTIONS = new Set(['url']);
  20 | 
  21 | /** @type {import('stylelint').StylelintRule} */


error: Unexpected character class range '0-9'. Use '\d' instead (regexp/prefer-d) at lib/rules/color-no-hex/index.js:17:17:
  15 | });
  16 | 
> 17 | const HEX = /^#[0-9A-Za-z]+/;
     |                 ^
  18 | const IGNORED_FUNCTIONS = new Set(['url']);
  19 | 
  20 | /** @type {import('stylelint').StylelintRule} */


error: Unexpected character class range '0-9'. Use '\d' instead (regexp/prefer-d) at lib/rules/color-no-invalid-hex/index.js:31:26:
  29 | 				if (type !== 'word') return;
  30 | 
> 31 | 				const hexMatch = /^#[0-9A-Za-z]+/.exec(value);
     | 				                     ^
  32 | 
  33 | 				if (!hexMatch) return;
  34 | 


error: '\r?' can be removed because it is already included by '\s*' (regexp/optimal-quantifier-concatenation) at lib/rules/declaration-colon-newline-after/index.js:67:14:
  65 | 							const betweenAfter = between.slice(sliceIndex);
  66 | 
> 67 | 							if (/^\s*\r?\n/.test(betweenAfter)) {
     | 							      ^
  68 | 								decl.raws.between = betweenBefore + betweenAfter.replace(/^[^\S\r\n]*/, '');
  69 | 							} else {
  70 | 								decl.raws.between = betweenBefore + context.newline + betweenAfter;


error: Capturing group number 1 is defined but never used (regexp/no-unused-capturing-group) at lib/rules/font-family-name-quotes/index.js:55:12:
  53 | function quotesRequired(family) {
  54 | 	return family.split(/\s+/).some((word) => {
> 55 | 		return /^(-?\d|--)/.test(word) || !/^[-_a-zA-Z0-9\u{00A0}-\u{10FFFF}]+$/u.test(word);
     | 		         ^
  56 | 	});
  57 | }
  58 | 


error: Unexpected character class ranges '[_a-zA-Z0-9]'. Use '\w' instead (regexp/prefer-w) at lib/rules/font-family-name-quotes/index.js:55:40:
  53 | function quotesRequired(family) {
  54 | 	return family.split(/\s+/).some((word) => {
> 55 | 		return /^(-?\d|--)/.test(word) || !/^[-_a-zA-Z0-9\u{00A0}-\u{10FFFF}]+$/u.test(word);
     | 		                                     ^
  56 | 	});
  57 | }
  58 | 


error: Unexpected character class range '0-9'. Use '\d' instead (regexp/prefer-d) at lib/rules/font-family-name-quotes/index.js:55:49:
  53 | function quotesRequired(family) {
  54 | 	return family.split(/\s+/).some((word) => {
> 55 | 		return /^(-?\d|--)/.test(word) || !/^[-_a-zA-Z0-9\u{00A0}-\u{10FFFF}]+$/u.test(word);
     | 		                                              ^
  56 | 	});
  57 | }
  58 | 


error: Unescaped source character '{' (regexp/strict) at lib/rules/function-calc-no-unspaced-operator/index.js:244:38:
  242 |  */
  243 | function blurVariables(source) {
> 244 | 	return source.replace(/[$@][^)\s]+|#{.+?}/g, '0');
      | 	                                    ^
  245 | }
  246 | 
  247 | /**


error: Unescaped source character '}' (regexp/strict) at lib/rules/function-calc-no-unspaced-operator/index.js:244:42:
  242 |  */
  243 | function blurVariables(source) {
> 244 | 	return source.replace(/[$@][^)\s]+|#{.+?}/g, '0');
      | 	                                        ^
  245 | }
  246 | 
  247 | /**


error: Unescaped source character '{' (regexp/strict) at lib/rules/indentation/index.js:306:36:
  304 | 						}
  305 | 
> 306 | 						const followsOpeningBrace = /{[ \t]*$/.test(source.slice(0, newlineIndex));
      | 						                             ^
  307 | 
  308 | 						if (followsOpeningBrace) {
  309 | 							parentheticalDepth += 1;


error: Unescaped source character '}' (regexp/strict) at lib/rules/indentation/index.js:312:44:
  310 | 						}
  311 | 
> 312 | 						const startingClosingBrace = /^[ \t]*}/.test(source.slice(match.startIndex + 1));
      | 						                                     ^
  313 | 
  314 | 						if (startingClosingBrace) {
  315 | 							parentheticalDepth -= 1;


error: Capturing group number 1 is defined but never used (regexp/no-unused-capturing-group) at lib/rules/indentation/index.js:610:17:
  608 | 
  609 | 		source = source.replace(/^[^\r\n]+/, (firstLine) => {
> 610 | 			if (/(?:^|\n)([ \t]*)$/.test(root.raws.beforeStart)) {
      | 			             ^
  611 | 				return RegExp.$1 + firstLine;
  612 | 			}
  613 | 


error: 'RegExp.$1' static property is forbidden (regexp/no-legacy-features) at lib/rules/indentation/index.js:611:12:
  609 | 		source = source.replace(/^[^\r\n]+/, (firstLine) => {
  610 | 			if (/(?:^|\n)([ \t]*)$/.test(root.raws.beforeStart)) {
> 611 | 				return RegExp.$1 + firstLine;
      | 				       ^
  612 | 			}
  613 | 
  614 | 			return '';


error: Capturing group number 1 is defined but never used (regexp/no-unused-capturing-group) at lib/rules/max-empty-lines/index.js:164:12:
  162 | 			const emptyCRLFLines = '\r\n'.repeat(repeatTimes);
  163 | 
> 164 | 			return /(\r\n)+/g.test(str)
      | 			        ^
  165 | 				? str.replace(/(\r\n)+/g, ($1) => {
  166 | 						if ($1.length / 2 > repeatTimes) {
  167 | 							return emptyCRLFLines;


warning: The 'g' flag is unnecessary because the regex is used only once in 'RegExp.prototype.test' (regexp/no-useless-flag) at lib/rules/max-empty-lines/index.js:164:20:
  162 | 			const emptyCRLFLines = '\r\n'.repeat(repeatTimes);
  163 | 
> 164 | 			return /(\r\n)+/g.test(str)
      | 			                ^
  165 | 				? str.replace(/(\r\n)+/g, ($1) => {
  166 | 						if ($1.length / 2 > repeatTimes) {
  167 | 							return emptyCRLFLines;


error: '\r?' can be removed because it is already included by '\s*' (regexp/optimal-quantifier-concatenation) at lib/rules/media-query-list-comma-newline-after/index.js:68:19:
  66 | 
  67 | 						if (primary.startsWith('always')) {
> 68 | 							params = /^\s*\r?\n/.test(afterComma)
     | 							           ^
  69 | 								? beforeComma + afterComma.replace(/^[^\S\r\n]*/, '')
  70 | 								: beforeComma + context.newline + afterComma;
  71 | 						} else if (primary.startsWith('never')) {


error: Capturing group number 2 is defined but never used (regexp/no-unused-capturing-group) at lib/rules/mediaQueryListCommaWhitespaceChecker.js:32:40:
  30 | 				}
  31 | 
> 32 | 				if ((execResult = /^([^\S\r\n]*\/\/([\s\S]*?))\r?\n/.exec(params.slice(index + 1)))) {
     | 				                                   ^
  33 | 					index += execResult[1].length;
  34 | 				}
  35 | 			}


error: The quantifier '\d*?' can exchange characters with '0+'. Using any string accepted by /0+/, this can be exploited to cause at least polynomial backtracking (regexp/no-super-linear-backtracking) at lib/rules/number-no-trailing-zeros/index.js:55:23:
  53 | 				}
  54 | 
> 55 | 				const match = /\.(\d*?)(0+)(?:\D|$)/.exec(valueNode.value);
     | 				                  ^
  56 | 
  57 | 				// match[1] is any numbers between the decimal and our trailing zero, could be empty
  58 | 				// match[2] is our trailing zero(s)


error: Unescaped source character ']' (regexp/strict) at lib/rules/selector-disallowed-list/__tests__/index.js:7:33:
   5 | testRule({
   6 | 	ruleName,
>  7 | 	config: ['a > .foo', /\[data-.+]/],
     | 	                               ^
   8 | 
   9 | 	accept: [
  10 | 		{


error: The quantifier '.*' can exchange characters with '.*'. Using any string accepted by />+/, this can be exploited to cause at least polynomial backtracking. This might cause exponential backtracking (regexp/no-super-linear-backtracking) at lib/rules/selector-disallowed-list/__tests__/index.js:63:17:
  61 | testRule({
  62 | 	ruleName,
> 63 | 	config: [/\.foo.*>.*\.bar/],
     | 	               ^
  64 | 
  65 | 	accept: [
  66 | 		{


error: Capturing group number 1 is defined but never used (regexp/no-unused-capturing-group) at lib/rules/string-no-newline/index.js:15:20:
  13 | 
  14 | const ruleName = 'string-no-newline';
> 15 | const reNewLine = /(\r?\n)/;
     |                    ^
  16 | 
  17 | const messages = ruleMessages(ruleName, {
  18 | 	rejected: 'Unexpected newline in string',


error: 'RegExp.leftContext' static property is forbidden (regexp/no-legacy-features) at lib/rules/string-no-newline/index.js:65:7:
  63 | 						attributeNode.operator,
  64 | 						// length of the contents before newline
> 65 | 						RegExp.leftContext,
     | 						^
  66 | 					].reduce(
  67 | 						(index, str) => index + str.length,
  68 | 						// index of the start of our attribute node in our source


error: 'RegExp.leftContext' static property is forbidden (regexp/no-legacy-features) at lib/rules/string-no-newline/index.js:99:6:
   97 | 					valueNode.quote,
   98 | 					// length of the contents before newline
>  99 | 					RegExp.leftContext,
      | 					^
  100 | 				].reduce((index, str) => index + str.length, valueNode.sourceIndex);
  101 | 
  102 | 				report({


error: Capturing group number 2 is defined but never used (regexp/no-unused-capturing-group) at lib/rules/unit-disallowed-list/index.js:28:11:
  26 | 	const value = mediaFeatureNode.value.toLowerCase();
  27 | 
> 28 | 	return /((-?\w*)*)/i.exec(value)[1];
     | 	         ^
  29 | };
  30 | 
  31 | function rule(listInput, options) {


warning: The 'i' flag is unnecessary because the pattern only contains case-invariant characters (regexp/no-useless-flag) at lib/rules/unit-disallowed-list/index.js:28:21:
  26 | 	const value = mediaFeatureNode.value.toLowerCase();
  27 | 
> 28 | 	return /((-?\w*)*)/i.exec(value)[1];
     | 	                   ^
  29 | };
  30 | 
  31 | function rule(listInput, options) {


error: Unexpected character class '[0-9]'. Use '\d' instead (regexp/prefer-d) at lib/utils/checkInvalidCLIOptions.js:54:48:
  52 |  */
  53 | const kebabCase = (opt) => {
> 54 | 	const matches = opt.match(/[A-Z]?[a-z]+|[A-Z]|[0-9]+/g);
     | 	                                              ^
  55 | 
  56 | 	if (matches) {
  57 | 		return matches.map((s) => s.toLowerCase()).join('-');


error: Unescaped source character '{' (regexp/strict) at lib/utils/hasLessInterpolation.js:10:11:
   8 |  */
   9 | module.exports = function (string) {
> 10 | 	return /@{.+?}/.test(string);
     | 	         ^
  11 | };
  12 | 


error: Unescaped source character '}' (regexp/strict) at lib/utils/hasLessInterpolation.js:10:15:
   8 |  */
   9 | module.exports = function (string) {
> 10 | 	return /@{.+?}/.test(string);
     | 	             ^
  11 | };
  12 | 


error: Unescaped source character '{' (regexp/strict) at lib/utils/hasScssInterpolation.js:9:11:
   7 |  */
   8 | module.exports = function (string) {
>  9 | 	return /#{.+?}/.test(string);
     | 	         ^
  10 | };
  11 | 


error: Unescaped source character '}' (regexp/strict) at lib/utils/hasScssInterpolation.js:9:15:
   7 |  */
   8 | module.exports = function (string) {
>  9 | 	return /#{.+?}/.test(string);
     | 	             ^
  10 | };
  11 | 


error: Unescaped source character '{' (regexp/strict) at lib/utils/hasTplInterpolation.js:10:10:
   8 |  */
   9 | module.exports = function (string) {
> 10 | 	return /{.+?}/.test(string);
     | 	        ^
  11 | };
  12 | 


error: Unescaped source character '}' (regexp/strict) at lib/utils/hasTplInterpolation.js:10:14:
   8 |  */
   9 | module.exports = function (string) {
> 10 | 	return /{.+?}/.test(string);
     | 	            ^
  11 | };
  12 | 


error: The quantifier '\d+' can exchange characters with '\d*'. Using any string accepted by /\d+/, this can be exploited to cause at least polynomial backtracking (regexp/no-super-linear-backtracking) at lib/utils/isKeyframeSelector.js:17:11:
  15 | 
  16 | 	// Percentages
> 17 | 	if (/^(?:\d+\.?\d*|\d*\.?\d+)%$/.test(selector)) {
     | 	         ^
  18 | 		return true;
  19 | 	}
  20 | 


error: The quantifier '\d*' can exchange characters with '\d+'. Using any string accepted by /\d+/, this can be exploited to cause at least polynomial backtracking (regexp/no-super-linear-backtracking) at lib/utils/isKeyframeSelector.js:17:21:
  15 | 
  16 | 	// Percentages
> 17 | 	if (/^(?:\d+\.?\d*|\d*\.?\d+)%$/.test(selector)) {
     | 	                   ^
  18 | 		return true;
  19 | 	}
  20 | 


error: Unescaped source character '{' (regexp/strict) at lib/utils/isStandardSyntaxMediaFeatureName.js:11:8:
   9 | module.exports = function (mediaFeatureName) {
  10 | 	// SCSS interpolation
> 11 | 	if (/#{.+?}|\$.+?/.test(mediaFeatureName)) {
     | 	      ^
  12 | 		return false;
  13 | 	}
  14 | 


error: Unescaped source character '}' (regexp/strict) at lib/utils/isStandardSyntaxMediaFeatureName.js:11:12:
   9 | module.exports = function (mediaFeatureName) {
  10 | 	// SCSS interpolation
> 11 | 	if (/#{.+?}|\$.+?/.test(mediaFeatureName)) {
     | 	          ^
  12 | 		return false;
  13 | 	}
  14 | 


warning: The quantifier can be removed because the quantifier is lazy and has a minimum of 1 (regexp/no-lazy-ends) at lib/utils/isStandardSyntaxMediaFeatureName.js:11:16:
   9 | module.exports = function (mediaFeatureName) {
  10 | 	// SCSS interpolation
> 11 | 	if (/#{.+?}|\$.+?/.test(mediaFeatureName)) {
     | 	              ^
  12 | 		return false;
  13 | 	}
  14 | 


error: Capturing group number 1 is defined but never used (regexp/no-unused-capturing-group) at lib/utils/isStandardSyntaxSelector.js:28:14:
  26 | 
  27 | 	// Less :extend()
> 28 | 	if (/:extend(\(.*?\))?/.test(selector)) {
     | 	            ^
  29 | 		return false;
  30 | 	}
  31 | 


warning: The 'i' flag is unnecessary because the pattern only contains case-invariant characters (regexp/no-useless-flag) at lib/utils/isStandardSyntaxSelector.js:33:24:
  31 | 
  32 | 	// Less mixin with resolved nested selectors (e.g. .foo().bar or .foo(@a, @b)[bar])
> 33 | 	if (/\.[\w-]+\(.*\).+/i.test(selector)) {
     | 	                      ^
  34 | 		return false;
  35 | 	}
  36 | 


error: Unexpected obscure character range. The characters of '+-/' (U+002b - U+002f) are not obvious (regexp/no-obscure-range) at lib/utils/isStandardSyntaxUrl.js:43:35:
  41 | 	// But in this case it is allowed to use only specific characters
  42 | 	// Also forbidden "/" at the end of url
> 43 | 	if (url.includes('$') && /^[$\s\w+-/*'"/]+$/.test(url) && !url.endsWith('/')) {
     | 	                                 ^
  44 | 		return false;
  45 | 	}
  46 | 


error: '/' (U+002f) is already included in '+-/' (U+002b - U+002f) (regexp/no-dupe-characters-character-class) at lib/utils/isStandardSyntaxUrl.js:43:41:
  41 | 	// But in this case it is allowed to use only specific characters
  42 | 	// Also forbidden "/" at the end of url
> 43 | 	if (url.includes('$') && /^[$\s\w+-/*'"/]+$/.test(url) && !url.endsWith('/')) {
     | 	                                       ^
  44 | 		return false;
  45 | 	}
  46 | 


error: Unexpected character class range '0-9'. Use '\d' instead (regexp/prefer-d) at lib/utils/isValidHex.js:10:16:
   8 |  */
   9 | module.exports = function (value) {
> 10 | 	return /^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value);
     | 	              ^
  11 | };
  12 | 


error: Unexpected character class range '0-9'. Use '\d' instead (regexp/prefer-d) at lib/utils/isValidHex.js:10:33:
   8 |  */
   9 | module.exports = function (value) {
> 10 | 	return /^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value);
     | 	                               ^
  11 | };
  12 | 


error: Unexpected character class range '0-9'. Use '\d' instead (regexp/prefer-d) at lib/utils/isValidHex.js:10:48:
   8 |  */
   9 | module.exports = function (value) {
> 10 | 	return /^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value);
     | 	                                              ^
  11 | };
  12 | 


error: '\r?' can be removed because it is already included by '\s*' (regexp/optimal-quantifier-concatenation) at lib/utils/removeEmptyLinesAfter.js:12:69:
  10 |  */
  11 | module.exports = function removeEmptyLinesAfter(node, newline) {
> 12 | 	node.raws.after = node.raws.after ? node.raws.after.replace(/(\r?\n\s*\r?\n)+/g, newline) : '';
     | 	                                                                   ^
  13 | 
  14 | 	return node;
  15 | };


error: '\r?' can be removed because it is already included by '\s*' (regexp/optimal-quantifier-concatenation) at lib/utils/removeEmptyLinesBefore.js:12:72:
  10 |  */
  11 | module.exports = function removeEmptyLinesBefore(node, newline) {
> 12 | 	node.raws.before = node.raws.before ? node.raws.before.replace(/(\r?\n\s*\r?\n)+/g, newline) : '';
     | 	                                                                      ^
  13 | 
  14 | 	return node;
  15 | };


45 errors and 4 warnings found.
31 errors and 3 warnings potentially fixable with the `--fix` option.

Summary:

45 errors and 4 warnings found.
31 errors and 3 warnings potentially fixable with the `--fix` option.

See also stylelint/stylelint#5516

Resources

@ybiquitous ybiquitous added the status: needs discussion triage needs further discussion label Sep 5, 2021
@hudochenkov
Copy link
Member

I think it could be nice addition giving how many RegExps we use.

However, we should be careful. I think there could be false positives. For example:

error: Capturing group number 1 is defined but never used (regexp/no-unused-capturing-group) at lib/__tests__/standalone-syntax.test.js:23:39:
  21 | 		expect(results).toHaveLength(6);
  22 | 
> 23 | 		const safeParserExtensionsTest = /\.(css|pcss|postcss)$/i;
     | 		                                    ^
  24 | 
  25 | 		results
  26 | 			.filter((result) => !safeParserExtensionsTest.test(result.source))

Seems to me syntax is correct and it's impossible in this use case not have capturing group.

@ota-meshi
Copy link
Member

Hi @hudochenkov.
I agree that there may be some false positives, but I think we should use a non-capturing group for that example.

- const safeParserExtensionsTest = /\.(css|pcss|postcss)$/i;
+ const safeParserExtensionsTest = /\.(?:css|pcss|postcss)$/i;

@ybiquitous
Copy link
Member Author

@hudochenkov @ota-meshi Thanks for the feedback!

As @ota-meshi suggests, it seems safe to me to add ?: to safeParserExtensionsTest because safeParserExtensionsTest is used only with RegExp.prototype.test().


By the way, we'd like to avoid false positives as possible, so are there any rules that should be disabled in the plugin:regexp/recommended ruleset? @ota-meshi

@hudochenkov
Copy link
Member

@ota-meshi today I learned! Didn't know about non-capturing group :)

After some googling I see the difference.

Personally, I think capturing group are more understandable by developers and make code easier to read.

@ota-meshi
Copy link
Member

ota-meshi commented Sep 6, 2021

By the way, we'd like to avoid false positives as possible, so are there any rules that should be disabled in the plugin:regexp/recommended ruleset?

Regarding the rule that verifies only regex, I think that there are almost no false positives because it is also used in Prism, which makes heavy use of regex. (The maintainer who is not me is also the maintainer of Prism.)
The error reported by the no-super-linear-xxx rule may need to be replaced with JavaScript logic to fix it, but it reports the same possibility as previously reported RedoS attacks. So popular packages like "stylelint" may need to be fixed.

Looking at this report, I noticed that there was a false positive for no-unused-capturing-group when using the static properties of RegExp (Legacy features).
If we don't fix no-legacy-features rule, we'll probably have a lot of false positives about no-unused-capturing-group and no-useless-flag (g and y flags).

I think the no-obscure-range rule can be turned off or overridden depending on your team's preferences. This is a rule to prevent problems that are not familiar with the country-specific unicode order, but I think the international OSS team can avoid the problem by reviewing.

Personally, I think capturing group are more understandable by developers and make code easier to read.

We can turn the rule OFF or ON depending on our preference 😉.

(I found out that lot of mistakes in the regex I wrote, so I started creating eslint-plugin-regexp. So I could have written a lot of wrong regex in stylelint. So I apologize in advance.)

@ybiquitous
Copy link
Member Author

@ota-meshi Thank you so much for the detailed explanation! 👏🏼

My opinions:

@ybiquitous
Copy link
Member Author

I've created stylelint/stylelint#5516 as an experiment. Please check it out. 😃

@jeddy3
Copy link
Member

jeddy3 commented Sep 6, 2021

SGTM. It found 49 problems in the stylelint code. It can autofix 34 of them, which is nice.

It'll be interesting to see if it caught stylelint/stylelint#5128, which is one of our 14.0.0 tasks.

ybiquitous added a commit that referenced this issue Sep 14, 2021
This change is created by:
- the `npm i eslint-plugin-regexp` command
- a few additions to the `.eslintrc.js` file.

Close #141
@ybiquitous
Copy link
Member Author

Hi there, thank you for providing feedback! I've created PR #142.

@ybiquitous ybiquitous added status: ask to implement ask before implementing as may no longer be relevant and removed status: needs discussion triage needs further discussion labels Sep 14, 2021
@ybiquitous ybiquitous self-assigned this Sep 14, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: ask to implement ask before implementing as may no longer be relevant
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants