Skip to content

Commit

Permalink
feat: handle thrown errors of autofix function
Browse files Browse the repository at this point in the history
  • Loading branch information
AndyOGo committed Jul 16, 2022
1 parent 47b6e79 commit 868ae5c
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 22 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1360,6 +1360,8 @@ This plugin supports **configurable** [autofixing enabled by `--fix` option](htt
So you have to supply an `autoFixFunc` function and **implement each fix you want by yourself**. To help you with that this function receives the whole [PostCSS API](http://api.postcss.org/postcss.html), all validations and configuration of this plugin, as follows [`node`](http://api.postcss.org/Node.html), `validation`, [`root`](http://api.postcss.org/Declaration.html#root) and `config`.
`validation` is a hash of `{ validVar, validFunc, validValue, validKeyword, longhandProp, longhandValue }`, which tells you which aspect of the rule failed validation.

You may `throw` errors if autofixing is not possible.

**Note:** it's best you use a JavaScript based config file, which is easy because Stylelint utilizes [cosmiconfig](https://github.com/davidtheclark/cosmiconfig).
Alternatively you can specify a common JS module, which will be resolved by [standard `require`](https://nodejs.org/api/modules.html#modules_file_modules) calls including support for `CWD`.

Expand All @@ -1379,6 +1381,14 @@ function autoFixFunc(node, validation, root, config) {
case 'red':
// auto-fix by PostCSS AST tranformation
node.value = '$color-red'

default:
// optional, you can throw your own error message if the value is not stated or handled, ex: color: blue
throw `Property ${prop} with value ${value} can't be autofixed!`
// or an Error object
throw new Error(`Property ${prop} with value ${value} can't be autofixed!`)
// or a falsy value to use the default error message
throw null;
}
}
}
Expand Down
61 changes: 41 additions & 20 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
getIgnoredKeywords,
getIgnoredValues,
getAutoFixFunc,
failedToFix,
} from './lib/validation';
import defaults, {
ruleName,
Expand All @@ -24,6 +25,7 @@ import defaults, {
const { utils } = stylelint;
const messages = utils.ruleMessages(ruleName, {
expected,
failedToFix,
});
/**
* RegExp to skip non-CSS properties.
Expand Down Expand Up @@ -416,24 +418,43 @@ const ruleFunction: StylelintPlugin<PrimaryOptions, SecondaryOptions> =

// support auto fixing
if (context.fix && !disableFix && autoFixFuncNormalized) {
const fixedValue = autoFixFuncNormalized(
node,
{
validVar,
validFunc,
validKeyword,
validValue,
longhandProp,
longhandValue,
},
root,
config
);

// apply fixed value if returned
if (fixedValue) {
// eslint-disable-next-line no-param-reassign
node.value = fixedValue;
try {
const fixedValue = autoFixFuncNormalized(
node,
{
validVar,
validFunc,
validKeyword,
validValue,
longhandProp,
longhandValue,
},
root,
config
);

// apply fixed value if returned
if (fixedValue) {
// eslint-disable-next-line no-param-reassign
node.value = fixedValue;
}
} catch (error) {
console.error(
error,
messages.failedToFix(error, value, nodeProp)
);
const { raws } = node;
// eslint-disable-next-line prefer-destructuring
const start = node.source!.start;

utils.report({
ruleName,
result,
node,
line: start!.line,
column: start!.column + nodeProp.length + raws.between!.length,
message: messages.failedToFix(error, value, nodeProp),
} as any);
}
} else {
const { raws } = node;
Expand All @@ -445,9 +466,9 @@ const ruleFunction: StylelintPlugin<PrimaryOptions, SecondaryOptions> =
result,
node,
line: start!.line,
// column: start!.column + nodeProp.length + raws.between!.length,
column: start!.column + nodeProp.length + raws.between!.length,
message: messages.expected(types, value, nodeProp, message),
});
} as any);
}

return true;
Expand Down
22 changes: 22 additions & 0 deletions src/lib/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,28 @@ export function expected(
return `Expected ${typesMessage} for "${value}" of "${property}"`;
}

/**
* Build failed-to-fix message for stylelint report.
*
* @internal
* @param error - An expression to `throw`.
* @param value - The CSS declaration's value.
* @param property - The CSS declaration's property.
*
* @returns Returns an failed-to-fix message for stylelint report.
*/
export function failedToFix(
error: unknown,
value: string,
property: string
): string {
if (error && (typeof error === 'string' || error instanceof Error)) {
return typeof error === 'string' ? error : error.message;
}

return `Property "${property}" with value "${value}" can't be autofixed`;
}

/**
* Get configured types for stylelint report message.
*
Expand Down
26 changes: 24 additions & 2 deletions test/auto-fix-func.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import testRule from 'stylelint-test-rule-tape';

import declarationStrictValue, { ruleName } from '../src';
import autoFixFunc from './helpers/auto-fix-func';
import autoFixFuncWithThrow from './helpers/auto-fix-func-with-throw';

const { rule } = declarationStrictValue;

Expand Down Expand Up @@ -46,16 +47,37 @@ testRule(
skipBasicChecks: true,

config: [
'color',
['color', 'font-size', 'display'],
{
autoFixFunc,
autoFixFunc: autoFixFuncWithThrow,
},
],

accept: [
{ code: '.foo { color: #fff; }' },
{ code: '.foo { color: red; }' },
],

reject: [
{
code: '.foo { font-size: 16px; }',
message: `"font-size" is not a color property (${ruleName})`,
line: 1,
column: 8,
},
{
code: '.foo { color: blue; }',
message: `Can't fix color "blue" (${ruleName})`,
line: 1,
column: 8,
},
{
code: '.foo { display: block; }',
message: `Property "display" with value "block" can't be autofixed (${ruleName})`,
line: 1,
column: 8,
},
],
}
);

Expand Down
31 changes: 31 additions & 0 deletions test/helpers/auto-fix-func-with-throw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// eslint-disable-next-line consistent-return, no-unused-vars, @typescript-eslint/no-unused-vars
function autoFixFunc(node, validation, root, config) {
const { value, prop } = node;

if (prop === 'color') {
// eslint-disable-next-line default-case
switch (value) {
case '#fff':
// auto-fix by returned value
return '$color-white';

case 'red':
// auto-fix by PostCSS AST tranformation
// eslint-disable-next-line no-param-reassign
node.value = '$color-red';
// eslint-disable-next-line consistent-return
return;

default:
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw `Can't fix color "${value}"`;
}
} else if (prop === 'font-size') {
throw new Error(`"${prop}" is not a color property`);
}

// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw null;
}

module.exports = autoFixFunc;

0 comments on commit 868ae5c

Please sign in to comment.