@jhae/stylelint-config-verifier 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Jan Hähne | https://github.com/jhae-de
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,151 @@
1
+ ![Version](https://img.shields.io/npm/v/%40jhae/stylelint-config-verifier?label=Version)
2
+ ![License](https://img.shields.io/github/license/jhae-de/stylelint-config-verifier?label=License&color=lightgrey)
3
+ ![Tests](https://img.shields.io/github/actions/workflow/status/jhae-de/stylelint-config-verifier/analyze.yaml?label=Tests)
4
+ ![Coverage](https://img.shields.io/codecov/c/github/jhae-de/stylelint-config-verifier/main?label=Coverage)
5
+
6
+ # Stylelint Config Verifier
7
+
8
+ Does your [Stylelint](https://github.com/stylelint/stylelint) configuration meet your expectations? Easily test
9
+ individual rules with [Jest](https://github.com/jestjs/jest) using code snippets and ensure that errors and warnings are
10
+ being reported correctly.
11
+
12
+ ## Installation
13
+
14
+ Using npm:
15
+
16
+ ```shell
17
+ npm install --save-dev @jhae/stylelint-config-verifier
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ### With inline code
23
+
24
+ Example Stylelint configuration file:
25
+
26
+ ```yaml
27
+ rules:
28
+ at-rule-disallowed-list:
29
+ - debug
30
+ - import
31
+ ```
32
+
33
+ Verifying the `at-rule-disallowed-list` rule configuration:
34
+
35
+ ```javascript
36
+ import { ConfigVerifier } from '@jhae/stylelint-config-verifier';
37
+
38
+ new ConfigVerifier().verify(
39
+ // Pass the name of the rule.
40
+ 'at-rule-disallowed-list',
41
+
42
+ // Describe one or more test cases.
43
+ {
44
+ // Give the test case a name.
45
+ name: 'Disallow @debug rule',
46
+
47
+ // Place the code to be tested against the configuration file here.
48
+ code: '@debug "";',
49
+
50
+ // Define the expectation.
51
+ expect: {
52
+ // Whether Stylelint should report an error or not.
53
+ errored: true,
54
+
55
+ // The messages that Stylelint should report.
56
+ messages: ['Unexpected at-rule "debug"'],
57
+
58
+ // The severities that Stylelint should report for each message.
59
+ severities: ['error'],
60
+ },
61
+ },
62
+ {
63
+ name: 'Disallow @import rule',
64
+ code: `
65
+ @import "test-1.css";
66
+ @import "test-2.css";
67
+ `,
68
+ expect: {
69
+ errored: true,
70
+ messages: ['Unexpected at-rule "import"', 'Unexpected at-rule "import"'],
71
+ severities: ['error', 'error'],
72
+ },
73
+ },
74
+ {
75
+ // The expectation can be omitted if Stylelint should not report errors.
76
+ name: 'Allow @use rule',
77
+ code: '@use "test.scss";',
78
+ },
79
+ );
80
+ ```
81
+
82
+ The verification result:
83
+
84
+ ```shell
85
+ Rule 'at-rule-disallowed-list'
86
+ ✓ Disallow @debug rule (1 ms)
87
+ ✓ Disallow @import rule (1 ms)
88
+ ✓ Allow @use rule (1 ms)
89
+ ```
90
+
91
+ ### With a file
92
+
93
+ It is also possible to pass a file to the test case. This is useful for testing overrides, for example.
94
+
95
+ Example Stylelint configuration file:
96
+
97
+ ```yaml
98
+ rules:
99
+ at-rule-disallowed-list:
100
+ - import
101
+
102
+ overrides:
103
+ - files:
104
+ - '*.css'
105
+ - '**/*.css'
106
+ rules:
107
+ at-rule-disallowed-list: null
108
+ ```
109
+
110
+ The CSS file, for example `at-rule-disallowed-list.css`:
111
+
112
+ ```css
113
+ @import 'test.css';
114
+ ```
115
+
116
+ Verifying the `at-rule-disallowed-list` rule configuration:
117
+
118
+ ```javascript
119
+ import { ConfigVerifier } from '@jhae/stylelint-config-verifier';
120
+
121
+ new ConfigVerifier().verify('at-rule-disallowed-list', {
122
+ name: 'Allow @import rule in CSS files',
123
+
124
+ // Pass the file instead of inline code.
125
+ file: 'at-rule-disallowed-list.css',
126
+ });
127
+ ```
128
+
129
+ ### Specifying the Stylelint configuration file
130
+
131
+ By default, the config verifier looks for a `.stylelintrc.yaml` configuration file. If a different location or file name
132
+ is used, the path to it can be specified as a constructor argument.
133
+
134
+ ```javascript
135
+ import { ConfigVerifier } from '@jhae/stylelint-config-verifier';
136
+
137
+ new ConfigVerifier('path/to/stylelint.config.js').verify();
138
+ ```
139
+
140
+ ### Importing into CommonJS
141
+
142
+ ```javascript
143
+ const { ConfigVerifier } = require('@jhae/stylelint-config-verifier');
144
+
145
+ new ConfigVerifier().verify();
146
+ ```
147
+
148
+ ---
149
+
150
+ Check out the [Standard SCSS Stylelint Config](https://github.com/jhae-de/stylelint-config-standard-scss) tests for more
151
+ examples.
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ConfigVerifier = void 0;
7
+ const stylelint_1 = __importDefault(require("stylelint"));
8
+ /**
9
+ * ConfigVerifier class
10
+ */
11
+ class ConfigVerifier {
12
+ configFile;
13
+ /**
14
+ * The default test case expectation
15
+ *
16
+ * This expectation occurs if Stylelint reports no problems and is used if no expectation was defined in a test case.
17
+ *
18
+ * @type {TestCaseExpectation}
19
+ *
20
+ * @internal
21
+ */
22
+ defaultExpectation = {
23
+ errored: false,
24
+ messages: [],
25
+ severities: [],
26
+ };
27
+ /**
28
+ * Creates and initializes an object instance of the class.
29
+ *
30
+ * @param {string} configFile The path to the Stylelint config file whose rules should be verified
31
+ */
32
+ constructor(configFile = '.stylelintrc.yaml') {
33
+ this.configFile = configFile;
34
+ }
35
+ /**
36
+ * Verifies a rule configuration.
37
+ *
38
+ * @param {string} ruleName The name of the rule
39
+ * @param {TestCase[]} testCases The test cases
40
+ */
41
+ verify(ruleName, ...testCases) {
42
+ describe(`Rule '${ruleName}'`, () => {
43
+ test.each(testCases)('$name', async (testCase) => {
44
+ const warnings = this.getWarnings(ruleName, await this.getLinterResult(testCase));
45
+ const { expect: expectation = this.defaultExpectation } = testCase;
46
+ expect(this.getErrored(warnings)).toBe(expectation.errored);
47
+ expect(this.getMessages(warnings)).toStrictEqual(expectation.messages.map((message) => `${message} (${ruleName})`));
48
+ expect(this.getSeverities(warnings)).toStrictEqual(expectation.severities);
49
+ });
50
+ });
51
+ }
52
+ /**
53
+ * Runs Stylelint for the given test case and returns a Promise that resolves to the linter result.
54
+ *
55
+ * @internal
56
+ *
57
+ * @param {TestCase} testCase The test case
58
+ *
59
+ * @return {Promise<LinterResult>}
60
+ */
61
+ getLinterResult({ file, code }) {
62
+ if ([file, code].filter((value) => value === undefined).length !== 1) {
63
+ throw new Error('Though both "file" and "code" are optional, you must have one and cannot have both.');
64
+ }
65
+ return stylelint_1.default.lint({
66
+ configFile: this.configFile,
67
+ files: file,
68
+ code,
69
+ });
70
+ }
71
+ /**
72
+ * Returns the warnings for a rule from the given linter result.
73
+ *
74
+ * @internal
75
+ *
76
+ * @param {string} ruleName The name of the rule
77
+ * @param {LinterResult} linterResult The linter result
78
+ *
79
+ * @return {Warning[]}
80
+ */
81
+ getWarnings(ruleName, { results: lintResults }) {
82
+ return lintResults
83
+ .map(({ warnings }) => warnings)
84
+ .reduce((previous, current) => previous.concat(current), [])
85
+ .filter((warning) => warning.rule === ruleName);
86
+ }
87
+ /**
88
+ * Returns true if the given lint warnings contain an error, otherwise false.
89
+ *
90
+ * @internal
91
+ *
92
+ * @param {Warning[]} warnings The lint warnings
93
+ *
94
+ * @return {boolean}
95
+ */
96
+ getErrored(warnings) {
97
+ return this.getSeverities(warnings).some((severity) => severity === 'error');
98
+ }
99
+ /**
100
+ * Returns the messages of the given lint warnings.
101
+ *
102
+ * @internal
103
+ *
104
+ * @param {Warning[]} warnings The lint warnings
105
+ *
106
+ * @return {string[]}
107
+ */
108
+ getMessages(warnings) {
109
+ return warnings.map(({ text }) => text);
110
+ }
111
+ /**
112
+ * Returns the severities of the given lint warnings.
113
+ *
114
+ * @internal
115
+ *
116
+ * @param {Warning[]} warnings The lint warnings
117
+ *
118
+ * @return {Severity[]}
119
+ */
120
+ getSeverities(warnings) {
121
+ return warnings.map(({ severity }) => severity);
122
+ }
123
+ }
124
+ exports.ConfigVerifier = ConfigVerifier;
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1,117 @@
1
+ import stylelint from 'stylelint';
2
+ /**
3
+ * ConfigVerifier class
4
+ */
5
+ export class ConfigVerifier {
6
+ configFile;
7
+ /**
8
+ * The default test case expectation
9
+ *
10
+ * This expectation occurs if Stylelint reports no problems and is used if no expectation was defined in a test case.
11
+ *
12
+ * @type {TestCaseExpectation}
13
+ *
14
+ * @internal
15
+ */
16
+ defaultExpectation = {
17
+ errored: false,
18
+ messages: [],
19
+ severities: [],
20
+ };
21
+ /**
22
+ * Creates and initializes an object instance of the class.
23
+ *
24
+ * @param {string} configFile The path to the Stylelint config file whose rules should be verified
25
+ */
26
+ constructor(configFile = '.stylelintrc.yaml') {
27
+ this.configFile = configFile;
28
+ }
29
+ /**
30
+ * Verifies a rule configuration.
31
+ *
32
+ * @param {string} ruleName The name of the rule
33
+ * @param {TestCase[]} testCases The test cases
34
+ */
35
+ verify(ruleName, ...testCases) {
36
+ describe(`Rule '${ruleName}'`, () => {
37
+ test.each(testCases)('$name', async (testCase) => {
38
+ const warnings = this.getWarnings(ruleName, await this.getLinterResult(testCase));
39
+ const { expect: expectation = this.defaultExpectation } = testCase;
40
+ expect(this.getErrored(warnings)).toBe(expectation.errored);
41
+ expect(this.getMessages(warnings)).toStrictEqual(expectation.messages.map((message) => `${message} (${ruleName})`));
42
+ expect(this.getSeverities(warnings)).toStrictEqual(expectation.severities);
43
+ });
44
+ });
45
+ }
46
+ /**
47
+ * Runs Stylelint for the given test case and returns a Promise that resolves to the linter result.
48
+ *
49
+ * @internal
50
+ *
51
+ * @param {TestCase} testCase The test case
52
+ *
53
+ * @return {Promise<LinterResult>}
54
+ */
55
+ getLinterResult({ file, code }) {
56
+ if ([file, code].filter((value) => value === undefined).length !== 1) {
57
+ throw new Error('Though both "file" and "code" are optional, you must have one and cannot have both.');
58
+ }
59
+ return stylelint.lint({
60
+ configFile: this.configFile,
61
+ files: file,
62
+ code,
63
+ });
64
+ }
65
+ /**
66
+ * Returns the warnings for a rule from the given linter result.
67
+ *
68
+ * @internal
69
+ *
70
+ * @param {string} ruleName The name of the rule
71
+ * @param {LinterResult} linterResult The linter result
72
+ *
73
+ * @return {Warning[]}
74
+ */
75
+ getWarnings(ruleName, { results: lintResults }) {
76
+ return lintResults
77
+ .map(({ warnings }) => warnings)
78
+ .reduce((previous, current) => previous.concat(current), [])
79
+ .filter((warning) => warning.rule === ruleName);
80
+ }
81
+ /**
82
+ * Returns true if the given lint warnings contain an error, otherwise false.
83
+ *
84
+ * @internal
85
+ *
86
+ * @param {Warning[]} warnings The lint warnings
87
+ *
88
+ * @return {boolean}
89
+ */
90
+ getErrored(warnings) {
91
+ return this.getSeverities(warnings).some((severity) => severity === 'error');
92
+ }
93
+ /**
94
+ * Returns the messages of the given lint warnings.
95
+ *
96
+ * @internal
97
+ *
98
+ * @param {Warning[]} warnings The lint warnings
99
+ *
100
+ * @return {string[]}
101
+ */
102
+ getMessages(warnings) {
103
+ return warnings.map(({ text }) => text);
104
+ }
105
+ /**
106
+ * Returns the severities of the given lint warnings.
107
+ *
108
+ * @internal
109
+ *
110
+ * @param {Warning[]} warnings The lint warnings
111
+ *
112
+ * @return {Severity[]}
113
+ */
114
+ getSeverities(warnings) {
115
+ return warnings.map(({ severity }) => severity);
116
+ }
117
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@jhae/stylelint-config-verifier",
3
+ "description": "A Stylelint configuration tester for Jest that helps you verify your defined rules.",
4
+ "version": "1.0.0",
5
+ "main": "./dist/cjs/config-verifier.class.js",
6
+ "module": "./dist/esm/config-verifier.class.js",
7
+ "exports": {
8
+ "types": "./types/config-verifier.class.d.ts",
9
+ "require": "./dist/cjs/config-verifier.class.js",
10
+ "default": "./dist/esm/config-verifier.class.js"
11
+ },
12
+ "author": {
13
+ "name": "JHAE",
14
+ "url": "https://github.com/jhae-de"
15
+ },
16
+ "repository": {
17
+ "url": "https://github.com/jhae-de/stylelint-config-verifier.git",
18
+ "type": "git"
19
+ },
20
+ "homepage": "https://github.com/jhae-de/stylelint-config-verifier",
21
+ "license": "MIT",
22
+ "scripts": {
23
+ "build": "rm -rf ./dist && npm run-script build:cjs && npm run-script build:esm",
24
+ "build:cjs": "tsc --project tsconfig.cjs.json",
25
+ "build:esm": "tsc --project tsconfig.esm.json",
26
+ "build:types": "rm -rf ./types && tsc --project tsconfig.types.json",
27
+ "lint": "npm run-script lint:prettier && npm run-script lint:eslint",
28
+ "lint:eslint": "eslint .",
29
+ "lint:prettier": "prettier . --check",
30
+ "postbuild": "./bin/post-build.sh",
31
+ "test": "jest",
32
+ "test:coverage": "npm run-script test -- --coverage",
33
+ "test:watch": "npm run-script test -- --watchAll",
34
+ "test:watch:coverage": "npm run-script test:watch -- --coverage"
35
+ },
36
+ "devDependencies": {
37
+ "@eslint/js": "^9.15",
38
+ "@types/jest": "^29.5",
39
+ "eslint": "^9.15",
40
+ "eslint-config-prettier": "^9.1",
41
+ "eslint-plugin-prettier": "^5.2",
42
+ "globals": "^15.12",
43
+ "prettier": "^3.3",
44
+ "ts-jest": "^29.2",
45
+ "typescript": "^5.7",
46
+ "typescript-eslint": "^8.15"
47
+ },
48
+ "peerDependencies": {
49
+ "jest": "^29.0",
50
+ "stylelint": "^16.0"
51
+ },
52
+ "keywords": [
53
+ "config",
54
+ "configuration",
55
+ "jest",
56
+ "rule",
57
+ "rules",
58
+ "stylelint",
59
+ "stylelint-scss",
60
+ "stylelintrc",
61
+ "test",
62
+ "ts-jest",
63
+ "verify"
64
+ ]
65
+ }
@@ -0,0 +1,21 @@
1
+ import type { TestCase } from './type';
2
+
3
+ /**
4
+ * ConfigVerifier class
5
+ */
6
+ export declare class ConfigVerifier {
7
+ /**
8
+ * Creates and initializes an object instance of the class.
9
+ *
10
+ * @param {string} configFile The path to the Stylelint config file whose rules should be verified
11
+ */
12
+ constructor(configFile?: string);
13
+
14
+ /**
15
+ * Verifies a rule configuration.
16
+ *
17
+ * @param {string} ruleName The name of the rule
18
+ * @param {TestCase[]} testCases The test cases
19
+ */
20
+ verify(ruleName: string, ...testCases: TestCase[]): void;
21
+ }
@@ -0,0 +1,2 @@
1
+ export type { TestCaseExpectation } from './test-case-expectation.type';
2
+ export type { TestCase } from './test-case.type';
@@ -0,0 +1,7 @@
1
+ import type { Severity } from 'stylelint';
2
+
3
+ export type TestCaseExpectation = {
4
+ errored: boolean;
5
+ messages: string[];
6
+ severities: Severity[];
7
+ };
@@ -0,0 +1,8 @@
1
+ import type { TestCaseExpectation } from './index';
2
+
3
+ export type TestCase = {
4
+ name: string;
5
+ file?: string;
6
+ code?: string;
7
+ expect?: TestCaseExpectation;
8
+ };