Skip to content

Commit

Permalink
Added a color-variables-in-files rule (#7)
Browse files Browse the repository at this point in the history
* Moved some helpers into the root src

* Added color-variables-in-files rule
  • Loading branch information
DCzajkowski authored Jul 22, 2019
1 parent 1fab7f0 commit 34ad40c
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 11 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ In your `.stylelintrc` config:
|| [color-no-non-variables](./src/rules/color-no-non-variables/README.md) | Disallow usage of color literals (allows only variables) |
|| [no-floats-with-unit](./src/rules/no-floats-with-unit/README.md) | Disallow usage of floats with certain units |
|| [variables-in-files](./src/rules/variables-in-files/README.md) | Allow for variables to be declared only in specified files |
|| [color-variables-in-files](./src/rules/color-variables-in-files/README.md) | Allow for variables containing color values to be declared only in specified files |
<!-- /rules-declaration -->

## License
Expand Down
File renamed without changes.
5 changes: 5 additions & 0 deletions src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { names as colorNames, functions as colorFunctions } from './css-colors';

/**
* A regex matching units inside CSS values. It looks for a number (it may contain one period inside) followed by
* a string containing only letters or a percent sign (%). The matching group inside targets the unit itself.
Expand All @@ -9,3 +11,6 @@
* rgb(1, 1, 1); color(black alpha(15%))
*/
export const unitRegex = new RegExp('^(\\d*\\.?\\d+)([a-zA-Z]+|%)$');

export const isColorLiteral = (value: string) =>
value.startsWith('#') || colorNames.includes(value) || colorFunctions.includes(value);
4 changes: 1 addition & 3 deletions src/rules/color-no-non-variables/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import stylelint from 'stylelint';
import postcss from 'postcss';
import valueParser from 'postcss-value-parser';
import { functions as colorFunctions, names as colorNames } from './css-colors';
import { isColorLiteral } from '../../helpers';
import { namespace } from '../../constants';

export const ruleName = `${namespace}/color-no-non-variables`;
Expand All @@ -13,8 +13,6 @@ export const messages = stylelint.utils.ruleMessages(ruleName, {
export default function(options: boolean) {
return async function(postcssRoot: postcss.Root, postcssResult: postcss.Result) {
const isVariableDeclaration = (node: postcss.Declaration) => node.prop.startsWith('--');
const isColorLiteral = (value: string) =>
value.startsWith('#') || colorNames.includes(value) || colorFunctions.includes(value);
const validOptions = stylelint.utils.validateOptions(postcssResult, ruleName);

if (!validOptions || options !== true) {
Expand Down
46 changes: 46 additions & 0 deletions src/rules/color-variables-in-files/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# dczajkowski/color-variables-in-files
Requires variables containing colors to be declared in specified files.

## Usage
```json
{
"rules": {
"dczajkowski/color-variables-in-files": [["src/styles/variables.css"]]
}
}
```

For this config the following applies:

```css
/* This is allowed */

/* in src/styles/variables.css */

:root {
--color-blue: cornflowerblue;
--spacing-1: 1rem;
}

.a {
--color-white: white;
}

/* in src/styles/partials/_navbar.css */

.b {
--spacing-3: 3rem;
}

/* This is not allowed */

/* in src/styles/partials/_navbar.css */

:root {
--color-blue: cornflowerblue;
}

.a {
--color-white: white;
}
```
53 changes: 53 additions & 0 deletions src/rules/color-variables-in-files/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import stylelint from 'stylelint';
import postcss from 'postcss';
import { namespace } from '../../constants';
import { isColorLiteral } from '../../helpers';

export const ruleName = `${namespace}/color-variables-in-files`;
export const messages = stylelint.utils.ruleMessages(ruleName, {
illegalVariableDeclaration: (variable: string, file: string, allowedFiles: string[]) =>
`Illegal color variable '${variable}' declaration in file '${file}'. Allowed files: ${allowedFiles
.map(file => `'${file}'`)
.join(', ')}.`,
});

const isVariableDeclaration = (prop: string) => prop.startsWith('--');

export default function(allowedFiles: string[] = []) {
return async function(postcssRoot: postcss.Root, postcssResult: postcss.Result) {
const validOptions = stylelint.utils.validateOptions(postcssResult, ruleName);

if (!validOptions || !Array.isArray(allowedFiles)) {
return;
}

const rootPath = `${process.cwd()}/`;

postcssRoot.walkDecls(node => {
const {
prop,
value,
source: {
input: { file },
},
} = node;

if (!isVariableDeclaration(prop) || !isColorLiteral(value) || !file) {
return;
}

const relativePath = file.replace(rootPath, '');

if (allowedFiles.includes(relativePath)) {
return;
}

stylelint.utils.report({
message: messages.illegalVariableDeclaration(prop, relativePath, allowedFiles),
result: postcssResult,
ruleName,
node,
});
});
};
}
7 changes: 7 additions & 0 deletions tests/helpers/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface Warning {
line: number;
column: number;
rule: string;
severity: 'error';
text: string;
}
57 changes: 57 additions & 0 deletions tests/rules/color-variables-in-files.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { messages } from '../../src/rules/color-variables-in-files';
import test from 'tape';
import { lint } from 'stylelint';
import { Warning } from '../helpers/types';

const runStylelint = async (allowedFiles: string[], file: string) =>
lint({
code: `
/* 2 */ :root {
/* 3 */ --spacing-4: 1rem;
/* 4 */ --color-black: #333;
/* 5 */ }
/* 6 */
/* 7 */ .a {
/* 8 */ --spacing-8: 2rem;
/* 9 */ --color-blue: cornflowerblue;
/* 10 */ }
`,
codeFilename: file,
config: {
plugins: ['@dczajkowski/stylelint-rules'],
rules: {
'dczajkowski/color-variables-in-files': [allowedFiles],
},
},
});

test('accepts all variable declarations in allowed files', async t => {
t.plan(1);

const { errored } = await runStylelint(['vars/colors.css', 'vars/index.css'], 'vars/index.css');

t.false(errored);
});

test('disallows color variable declarations in not-allowed files', async t => {
t.plan(6);

const allowedFiles = ['vars/colors.css', 'vars/index.css'];
const file = 'partials/_navbar.css';

const {
errored,
results: [{ warnings: unknownWarnings }],
} = await runStylelint(allowedFiles, file);

const warnings = <Warning[]>(<unknown[]>unknownWarnings);

console.log(warnings[0]);

t.true(errored);
t.equals(warnings.length, 2, 'There should be two warnings emitted.');
t.equals(warnings[0].text, messages.illegalVariableDeclaration('--color-black', file, allowedFiles));
t.equals(warnings[0].line, 4);
t.equals(warnings[1].text, messages.illegalVariableDeclaration('--color-blue', file, allowedFiles));
t.equals(warnings[1].line, 9);
});
9 changes: 1 addition & 8 deletions tests/rules/variables-in-files.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { messages } from '../../src/rules/variables-in-files';
import test from 'tape';
import { lint } from 'stylelint';
import { Warning } from '../helpers/types';

const runStylelint = async (allowedFiles: string[], file: string) =>
lint({
Expand Down Expand Up @@ -30,14 +31,6 @@ test('accepts variable declarations in allowed files', async t => {
t.false(errored);
});

interface Warning {
line: number;
column: number;
rule: string;
severity: 'error';
text: string;
}

test('disallows variable declarations in not-allowed files', async t => {
t.plan(4);

Expand Down

0 comments on commit 34ad40c

Please sign in to comment.