stylelint-webpack-plugin 2.1.1 → 2.3.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/dist/linter.js CHANGED
@@ -5,108 +5,195 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = linter;
7
7
 
8
+ var _path = require("path");
9
+
10
+ var _arrify = _interopRequireDefault(require("arrify"));
11
+
8
12
  var _StylelintError = _interopRequireDefault(require("./StylelintError"));
9
13
 
14
+ var _getStylelint = _interopRequireDefault(require("./getStylelint"));
15
+
10
16
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
11
17
 
12
- /** @typedef {import('stylelint').LinterResult} LinterResult */
18
+ // @ts-ignore
19
+
20
+ /** @typedef {import('stylelint')} Stylelint */
13
21
 
14
22
  /** @typedef {import('stylelint').LintResult} LintResult */
15
23
 
16
24
  /** @typedef {import('webpack').Compiler} Compiler */
17
25
 
18
- /** @typedef {import('./getOptions').Options} Options */
26
+ /** @typedef {import('webpack').Compilation} Compilation */
19
27
 
20
- /**
21
- * @callback Lint
22
- * @param {Options} options
23
- * @returns {Promise<LinterResult>}
24
- */
28
+ /** @typedef {import('./options').Options} Options */
25
29
 
26
- /**
27
- * @callback LinterCallback
28
- * @param {StylelintError | null=} error
29
- * @returns {void}
30
- */
30
+ /** @typedef {import('./options').FormatterType} FormatterType */
31
+
32
+ /** @typedef {((results: LintResult[]) => string)} FormatterFunction */
33
+
34
+ /** @typedef {(compilation: Compilation) => Promise<void>} GenerateReport */
31
35
 
36
+ /** @typedef {{errors?: StylelintError, warnings?: StylelintError, generateReportAsset?: GenerateReport}} Report */
37
+
38
+ /** @typedef {() => Promise<Report>} Reporter */
39
+
40
+ /** @typedef {(files: string|string[]) => void} Linter */
41
+
42
+ /** @typedef {{[files: string]: LintResult}} LintResultMap */
43
+
44
+ /** @type {WeakMap<Compiler, LintResultMap>} */
45
+ const resultStorage = new WeakMap();
32
46
  /**
33
- * @param {Lint} lint
47
+ * @param {string|undefined} key
34
48
  * @param {Options} options
35
- * @param {Compiler} compiler
36
- * @param {LinterCallback} callback
37
- * @returns {void}
49
+ * @param {Compilation} compilation
50
+ * @returns {{lint: Linter, report: Reporter, threads: number}}
38
51
  */
39
- function linter(lint, options, compiler, callback) {
40
- /** @type {Array<LintResult>} */
41
- let errors = [];
42
- /** @type {Array<LintResult>} */
43
-
44
- let warnings = [];
45
- lint(options).then(({
46
- results
47
- }) => {
52
+
53
+ function linter(key, options, compilation) {
54
+ /** @type {Stylelint} */
55
+ let stylelint;
56
+ /** @type {(files: string|string[]) => Promise<LintResult[]>} */
57
+
58
+ let lintFiles;
59
+ /** @type {() => Promise<void>} */
60
+
61
+ let cleanup;
62
+ /** @type number */
63
+
64
+ let threads;
65
+ /** @type {Promise<LintResult[]>[]} */
66
+
67
+ const rawResults = [];
68
+ const crossRunResultStorage = getResultStorage(compilation);
69
+
70
+ try {
48
71
  ({
72
+ stylelint,
73
+ lintFiles,
74
+ cleanup,
75
+ threads
76
+ } = (0, _getStylelint.default)(key, options));
77
+ } catch (e) {
78
+ throw new _StylelintError.default(e.message);
79
+ }
80
+
81
+ return {
82
+ lint,
83
+ report,
84
+ threads
85
+ };
86
+ /**
87
+ * @param {string | string[]} files
88
+ */
89
+
90
+ function lint(files) {
91
+ for (const file of (0, _arrify.default)(files)) {
92
+ delete crossRunResultStorage[file];
93
+ }
94
+
95
+ rawResults.push(lintFiles(files).catch(e => {
96
+ compilation.errors.push(e);
97
+ return [];
98
+ }));
99
+ }
100
+
101
+ async function report() {
102
+ // Filter out ignored files.
103
+ let results = removeIgnoredWarnings( // Get the current results, resetting the rawResults to empty
104
+ await flatten(rawResults.splice(0, rawResults.length)));
105
+ await cleanup();
106
+
107
+ for (const result of results) {
108
+ crossRunResultStorage[String(result.source)] = result;
109
+ }
110
+
111
+ results = Object.values(crossRunResultStorage); // do not analyze if there are no results or stylelint config
112
+
113
+ if (!results || results.length < 1) {
114
+ return {};
115
+ }
116
+
117
+ const formatter = loadFormatter(stylelint, options.formatter);
118
+ const {
49
119
  errors,
50
120
  warnings
51
- } = parseResults(options, results));
52
- compiler.hooks.afterEmit.tapAsync('StylelintWebpackPlugin', (compilation, next) => {
53
- if (warnings.length) {
54
- // @ts-ignore
55
- compilation.warnings.push(_StylelintError.default.format(options, warnings));
56
- warnings = [];
57
- }
121
+ } = formatResults(formatter, parseResults(options, results));
122
+ return {
123
+ errors,
124
+ warnings,
125
+ generateReportAsset
126
+ };
127
+ /**
128
+ * @param {Compilation} compilation
129
+ * @returns {Promise<void>}
130
+ */
58
131
 
59
- if (errors.length) {
60
- // @ts-ignore
61
- compilation.errors.push(_StylelintError.default.format(options, errors));
62
- errors = [];
132
+ async function generateReportAsset({
133
+ compiler
134
+ }) {
135
+ const {
136
+ outputReport
137
+ } = options;
138
+ /**
139
+ * @param {string} name
140
+ * @param {string | Buffer} content
141
+ */
142
+
143
+ const save = (name, content) =>
144
+ /** @type {Promise<void>} */
145
+ new Promise((finish, bail) => {
146
+ const {
147
+ mkdir,
148
+ writeFile
149
+ } = compiler.outputFileSystem; // ensure directory exists
150
+ // @ts-ignore - the types for `outputFileSystem` are missing the 3 arg overload
151
+
152
+ mkdir((0, _path.dirname)(name), {
153
+ recursive: true
154
+ }, err => {
155
+ /* istanbul ignore if */
156
+ if (err) bail(err);else writeFile(name, content, err2 => {
157
+ /* istanbul ignore if */
158
+ if (err2) bail(err2);else finish();
159
+ });
160
+ });
161
+ });
162
+
163
+ if (!outputReport || !outputReport.filePath) {
164
+ return;
63
165
  }
64
166
 
65
- next();
66
- });
167
+ const content = outputReport.formatter ? loadFormatter(stylelint, outputReport.formatter)(results) : formatter(results);
168
+ let {
169
+ filePath
170
+ } = outputReport;
171
+
172
+ if (!(0, _path.isAbsolute)(filePath)) {
173
+ filePath = (0, _path.join)(compiler.outputPath, filePath);
174
+ }
67
175
 
68
- if (options.failOnError && errors.length) {
69
- callback(_StylelintError.default.format(options, errors));
70
- } else if (options.failOnWarning && warnings.length) {
71
- callback(_StylelintError.default.format(options, warnings));
72
- } else {
73
- callback();
176
+ await save(filePath, content);
74
177
  }
75
- }).catch(e => {
76
- compiler.hooks.afterEmit.tapAsync('StylelintWebpackPlugin', (compilation, next) => {
77
- // @ts-ignore
78
- compilation.errors.push(new _StylelintError.default(e.message));
79
- next();
80
- });
81
- callback();
82
- });
178
+ }
83
179
  }
84
180
  /**
85
- *
86
- * @param {Options} options
87
- * @param {Array<LintResult>} results
88
- * @returns {{errors: Array<LintResult>, warnings: Array<LintResult>}}
181
+ * @param {FormatterFunction} formatter
182
+ * @param {{ errors: LintResult[]; warnings: LintResult[]; }} results
183
+ * @returns {{errors?: StylelintError, warnings?: StylelintError}}
89
184
  */
90
185
 
91
186
 
92
- function parseResults(options, results) {
93
- /** @type {Array<LintResult>} */
94
- let errors = [];
95
- /** @type {Array<LintResult>} */
96
-
97
- let warnings = [];
98
-
99
- if (options.emitError) {
100
- errors = results.filter(file => fileHasErrors(file) || fileHasWarnings(file));
101
- } else if (options.emitWarning) {
102
- warnings = results.filter(file => fileHasErrors(file) || fileHasWarnings(file));
103
- } else {
104
- warnings = results.filter(file => !fileHasErrors(file) && fileHasWarnings(file));
105
- errors = results.filter(fileHasErrors);
187
+ function formatResults(formatter, results) {
188
+ let errors;
189
+ let warnings;
190
+
191
+ if (results.warnings.length > 0) {
192
+ warnings = new _StylelintError.default(formatter(results.warnings));
106
193
  }
107
194
 
108
- if (options.quiet && warnings.length) {
109
- warnings = [];
195
+ if (results.errors.length > 0) {
196
+ errors = new _StylelintError.default(formatter(results.errors));
110
197
  }
111
198
 
112
199
  return {
@@ -115,20 +202,99 @@ function parseResults(options, results) {
115
202
  };
116
203
  }
117
204
  /**
118
- * @param {LintResult} file
119
- * @returns {boolean}
205
+ * @param {Options} options
206
+ * @param {LintResult[]} results
207
+ * @returns {{errors: LintResult[], warnings: LintResult[]}}
208
+ */
209
+
210
+
211
+ function parseResults(options, results) {
212
+ /** @type {LintResult[]} */
213
+ const errors = [];
214
+ /** @type {LintResult[]} */
215
+
216
+ const warnings = [];
217
+ results.forEach(file => {
218
+ const fileErrors = file.warnings.filter(message => options.emitError && message.severity === 'error');
219
+
220
+ if (fileErrors.length > 0) {
221
+ errors.push({ ...file,
222
+ warnings: fileErrors
223
+ });
224
+ }
225
+
226
+ const fileWarnings = file.warnings.filter(message => options.emitWarning && message.severity === 'warning');
227
+
228
+ if (fileWarnings.length > 0) {
229
+ warnings.push({ ...file,
230
+ warnings: fileWarnings
231
+ });
232
+ }
233
+ });
234
+ return {
235
+ errors,
236
+ warnings
237
+ };
238
+ }
239
+ /**
240
+ * @param {Stylelint} stylelint
241
+ * @param {FormatterType=} formatter
242
+ * @returns {FormatterFunction}
120
243
  */
121
244
 
122
245
 
123
- function fileHasErrors(file) {
124
- return !!file.errored;
246
+ function loadFormatter(stylelint, formatter) {
247
+ if (typeof formatter === 'function') {
248
+ return formatter;
249
+ }
250
+
251
+ if (typeof formatter === 'string') {
252
+ try {
253
+ return stylelint.formatters[formatter];
254
+ } catch (_) {// Load the default formatter.
255
+ }
256
+ }
257
+
258
+ return stylelint.formatters.string;
125
259
  }
126
260
  /**
127
- * @param {LintResult} file
128
- * @returns {boolean}
261
+ * @param {LintResult[]} results
262
+ * @returns {LintResult[]}
129
263
  */
130
264
 
131
265
 
132
- function fileHasWarnings(file) {
133
- return file.warnings && file.warnings.length > 0;
266
+ function removeIgnoredWarnings(results) {
267
+ return results.filter(result => !result.ignored);
268
+ }
269
+ /**
270
+ * @param {Promise<LintResult[]>[]} results
271
+ * @returns {Promise<LintResult[]>}
272
+ */
273
+
274
+
275
+ async function flatten(results) {
276
+ /**
277
+ * @param {LintResult[]} acc
278
+ * @param {LintResult[]} list
279
+ */
280
+ const flat = (acc, list) => [...acc, ...list];
281
+
282
+ return (await Promise.all(results)).reduce(flat, []);
283
+ }
284
+ /**
285
+ * @param {Compilation} compilation
286
+ * @returns {LintResultMap}
287
+ */
288
+
289
+
290
+ function getResultStorage({
291
+ compiler
292
+ }) {
293
+ let storage = resultStorage.get(compiler);
294
+
295
+ if (!storage) {
296
+ resultStorage.set(compiler, storage = {});
297
+ }
298
+
299
+ return storage;
134
300
  }
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getOptions = getOptions;
7
+ exports.getStylelintOptions = getStylelintOptions;
8
+
9
+ var _schemaUtils = require("schema-utils");
10
+
11
+ var _options = _interopRequireDefault(require("./options.json"));
12
+
13
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
14
+
15
+ // @ts-ignore
16
+
17
+ /** @typedef {import("stylelint")} stylelint */
18
+
19
+ /** @typedef {import("stylelint").LinterOptions} StylelintOptions */
20
+
21
+ /** @typedef {import("stylelint").FormatterType} FormatterType */
22
+
23
+ /**
24
+ * @typedef {Object} OutputReport
25
+ * @property {string=} filePath
26
+ * @property {FormatterType=} formatter
27
+ */
28
+
29
+ /**
30
+ * @typedef {Object} PluginOptions
31
+ * @property {string} context
32
+ * @property {boolean} emitError
33
+ * @property {boolean} emitWarning
34
+ * @property {string|string[]=} exclude
35
+ * @property {string|string[]} extensions
36
+ * @property {boolean} failOnError
37
+ * @property {boolean} failOnWarning
38
+ * @property {string|string[]} files
39
+ * @property {FormatterType} formatter
40
+ * @property {boolean} lintDirtyModulesOnly
41
+ * @property {boolean} quiet
42
+ * @property {string} stylelintPath
43
+ * @property {OutputReport} outputReport
44
+ * @property {number|boolean=} threads
45
+ */
46
+
47
+ /** @typedef {Partial<PluginOptions & StylelintOptions>} Options */
48
+
49
+ /**
50
+ * @param {Options} pluginOptions
51
+ * @returns {Partial<PluginOptions>}
52
+ */
53
+ function getOptions(pluginOptions) {
54
+ const options = {
55
+ extensions: ['css', 'scss', 'sass'],
56
+ emitError: true,
57
+ emitWarning: true,
58
+ failOnError: true,
59
+ ...pluginOptions,
60
+ ...(pluginOptions.quiet ? {
61
+ emitError: true,
62
+ emitWarning: false
63
+ } : {})
64
+ }; // @ts-ignore
65
+
66
+ (0, _schemaUtils.validate)(_options.default, options, {
67
+ name: 'Stylelint Webpack Plugin',
68
+ baseDataPath: 'options'
69
+ });
70
+ return options;
71
+ }
72
+ /**
73
+ * @param {Options} pluginOptions
74
+ * @returns {Partial<StylelintOptions>}
75
+ */
76
+
77
+
78
+ function getStylelintOptions(pluginOptions) {
79
+ const stylelintOptions = { ...pluginOptions
80
+ }; // Keep the files and formatter option because it is common to both the plugin and Stylelint.
81
+
82
+ const {
83
+ files,
84
+ formatter,
85
+ ...stylelintOnlyOptions
86
+ } = _options.default.properties; // No need to guard the for-in because schema.properties has hardcoded keys.
87
+ // eslint-disable-next-line guard-for-in
88
+
89
+ for (const option in stylelintOnlyOptions) {
90
+ // @ts-ignore
91
+ delete stylelintOptions[option];
92
+ }
93
+
94
+ return stylelintOptions;
95
+ }
package/dist/options.json CHANGED
@@ -7,15 +7,23 @@
7
7
  "type": "string"
8
8
  },
9
9
  "emitError": {
10
- "description": "Will always return errors, if set to `true`.",
10
+ "description": "The errors found will always be emitted, to disable set to `false`.",
11
11
  "type": "boolean"
12
12
  },
13
13
  "emitWarning": {
14
- "description": "Will always return warnings, if set to `true`.",
14
+ "description": "The warnings found will always be emitted, to disable set to `false`.",
15
15
  "type": "boolean"
16
16
  },
17
+ "exclude": {
18
+ "description": "Specify the files and/or directories to exclude. Must be relative to `options.context`.",
19
+ "anyOf": [{ "type": "string" }, { "type": "array" }]
20
+ },
21
+ "extensions": {
22
+ "description": "Specify extensions that should be checked.",
23
+ "anyOf": [{ "type": "string" }, { "type": "array" }]
24
+ },
17
25
  "failOnError": {
18
- "description": "Will cause the module build to fail if there are any errors, if set to `true`.",
26
+ "description": "Will cause the module build to fail if there are any errors, to disable set to `false`.",
19
27
  "type": "boolean"
20
28
  },
21
29
  "failOnWarning": {
@@ -23,7 +31,7 @@
23
31
  "type": "boolean"
24
32
  },
25
33
  "files": {
26
- "description": "Specify the glob pattern for finding files. Must be relative to `options.context`.",
34
+ "description": "Specify directories, files, or globs. Must be relative to `options.context`. Directories are traveresed recursively looking for files matching `options.extensions`. File and glob patterns ignore `options.extensions`.",
27
35
  "anyOf": [{ "type": "string" }, { "type": "array" }]
28
36
  },
29
37
  "formatter": {
@@ -41,6 +49,32 @@
41
49
  "stylelintPath": {
42
50
  "description": "Path to `stylelint` instance that will be used for linting.",
43
51
  "type": "string"
52
+ },
53
+ "outputReport": {
54
+ "description": "Write the output of the errors to a file, for example a `json` file for use for reporting.",
55
+ "anyOf": [
56
+ {
57
+ "type": "boolean"
58
+ },
59
+ {
60
+ "type": "object",
61
+ "additionalProperties": false,
62
+ "properties": {
63
+ "filePath": {
64
+ "description": "The `filePath` is relative to the webpack config: `output.path`.",
65
+ "anyOf": [{ "type": "string" }]
66
+ },
67
+ "formatter": {
68
+ "description": "You can pass in a different formatter for the output file, if none is passed in the default/configured formatter will be used.",
69
+ "anyOf": [{ "type": "string" }, { "instanceof": "Function" }]
70
+ }
71
+ }
72
+ }
73
+ ]
74
+ },
75
+ "threads": {
76
+ "description": "Set to true for an auto-selected pool size based on number of cpus. Set to a number greater than 1 to set an explicit pool size. Set to false, 1, or less to disable and only run in main process.",
77
+ "anyOf": [{ "type": "number" }, { "type": "boolean" }]
44
78
  }
45
79
  }
46
80
  }
package/dist/utils.js CHANGED
@@ -3,29 +3,84 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.jsonStringifyReplacerSortKeys = void 0;
6
7
  exports.parseFiles = parseFiles;
7
- exports.replaceBackslashes = replaceBackslashes;
8
+ exports.parseFoldersToGlobs = parseFoldersToGlobs;
9
+
10
+ var _path = require("path");
11
+
12
+ var _fs = require("fs");
13
+
14
+ var _normalizePath = _interopRequireDefault(require("normalize-path"));
8
15
 
9
16
  var _arrify = _interopRequireDefault(require("arrify"));
10
17
 
11
18
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12
19
 
13
- const UNESCAPED_GLOB_SYMBOLS_RE = /(\\?)([()*?[\]{|}]|^!|[!+@](?=\())/g;
20
+ // @ts-ignore
21
+ // @ts-ignore
22
+
14
23
  /**
15
- * @param {Array<string> | string} files
24
+ * @param {string|(string|undefined)[]} files
16
25
  * @param {string} context
17
- * @returns {Array<string>}
26
+ * @returns {string[]}
18
27
  */
19
-
20
28
  function parseFiles(files, context) {
21
- return (0, _arrify.default)(files).map(file => `${replaceBackslashes(context).replace(UNESCAPED_GLOB_SYMBOLS_RE, '\\$2')}/${replaceBackslashes(file)}`);
29
+ return (0, _arrify.default)(files).filter((
30
+ /** @type {string} */
31
+ file) => typeof file === 'string').map((
32
+ /** @type {string} */
33
+ file) => (0, _normalizePath.default)((0, _path.resolve)(context, file)));
34
+ }
35
+ /**
36
+ * @param {string|string[]} patterns
37
+ * @param {string|string[]} extensions
38
+ * @returns {string[]}
39
+ */
40
+
41
+
42
+ function parseFoldersToGlobs(patterns, extensions = []) {
43
+ const extensionsList = (0, _arrify.default)(extensions);
44
+ const [prefix, postfix] = extensionsList.length > 1 ? ['{', '}'] : ['', ''];
45
+ const extensionsGlob = extensionsList.map((
46
+ /** @type {string} */
47
+ extension) => extension.replace(/^\./u, '')).join(',');
48
+ return (0, _arrify.default)(patterns).map((
49
+ /** @type {string} */
50
+ pattern) => {
51
+ try {
52
+ // The patterns are absolute because they are prepended with the context.
53
+ const stats = (0, _fs.statSync)(pattern);
54
+ /* istanbul ignore else */
55
+
56
+ if (stats.isDirectory()) {
57
+ return pattern.replace(/[/\\]*?$/u, `/**${extensionsGlob ? `/*.${prefix + extensionsGlob + postfix}` : ''}`);
58
+ }
59
+ } catch (_) {// Return the pattern as is on error.
60
+ }
61
+
62
+ return pattern;
63
+ });
22
64
  }
23
65
  /**
24
- * @param {string} str
25
- * @returns {string}
66
+ *
67
+ * @param {string} _ key, but unused
68
+ * @param {any} value
26
69
  */
27
70
 
28
71
 
29
- function replaceBackslashes(str) {
30
- return str.replace(/\\/g, '/');
31
- }
72
+ const jsonStringifyReplacerSortKeys = (_, value) => {
73
+ /**
74
+ * @param {{ [x: string]: any; }} sorted
75
+ * @param {string | number} key
76
+ */
77
+ const insert = (sorted, key) => {
78
+ // eslint-disable-next-line no-param-reassign
79
+ sorted[key] = value[key];
80
+ return sorted;
81
+ };
82
+
83
+ return value instanceof Object && !(value instanceof Array) ? Object.keys(value).sort().reduce(insert, {}) : value;
84
+ };
85
+
86
+ exports.jsonStringifyReplacerSortKeys = jsonStringifyReplacerSortKeys;
package/dist/worker.js ADDED
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+
3
+ /** @typedef {import('stylelint')} Stylelint */
4
+
5
+ /** @typedef {import("stylelint").LinterOptions} StylelintOptions */
6
+
7
+ /** @typedef {import('./options').Options} Options */
8
+ Object.assign(module.exports, {
9
+ lintFiles,
10
+ setup
11
+ });
12
+ /** @type {Stylelint} */
13
+
14
+ let stylelint;
15
+ /** @type {Partial<StylelintOptions>} */
16
+
17
+ let linterOptions;
18
+ /**
19
+ * @param {Options} options
20
+ * @param {Partial<StylelintOptions>} stylelintOptions
21
+ */
22
+
23
+ function setup(options, stylelintOptions) {
24
+ stylelint = require(options.stylelintPath || 'stylelint');
25
+ linterOptions = stylelintOptions;
26
+ return stylelint;
27
+ }
28
+ /**
29
+ * @param {string | string[]} files
30
+ */
31
+
32
+
33
+ async function lintFiles(files) {
34
+ const {
35
+ results
36
+ } = await stylelint.lint({ ...linterOptions,
37
+ files
38
+ }); // Reset result to work with worker
39
+
40
+ return results.map(result => {
41
+ return {
42
+ source: result.source,
43
+ errored: result.errored,
44
+ ignored: result.ignored,
45
+ warnings: result.warnings,
46
+ deprecations: result.deprecations,
47
+ invalidOptionWarnings: result.invalidOptionWarnings
48
+ };
49
+ });
50
+ }