stylelint-webpack-plugin 2.1.0 → 2.2.2

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/index.js CHANGED
@@ -7,9 +7,13 @@ exports.default = void 0;
7
7
 
8
8
  var _path = require("path");
9
9
 
10
- var _getOptions = _interopRequireDefault(require("./getOptions"));
10
+ var _arrify = _interopRequireDefault(require("arrify"));
11
11
 
12
- var _LintDirtyModulesPlugin = _interopRequireDefault(require("./LintDirtyModulesPlugin"));
12
+ var _globby = _interopRequireDefault(require("globby"));
13
+
14
+ var _micromatch = require("micromatch");
15
+
16
+ var _options = require("./options");
13
17
 
14
18
  var _linter = _interopRequireDefault(require("./linter"));
15
19
 
@@ -17,10 +21,29 @@ var _utils = require("./utils");
17
21
 
18
22
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
19
23
 
24
+ // @ts-ignore
25
+ // @ts-ignore
26
+
20
27
  /** @typedef {import('webpack').Compiler} Compiler */
28
+
29
+ /** @typedef {import('webpack').Module} Module */
30
+
31
+ /** @typedef {import('./options').Options} Options */
32
+
33
+ /** @typedef {Partial<{timestamp:number} | number>} FileSystemInfoEntry */
34
+ const STYLELINT_PLUGIN = 'StylelintWebpackPlugin';
35
+ let counter = 0;
36
+
21
37
  class StylelintWebpackPlugin {
38
+ /**
39
+ * @param {Options} options
40
+ */
22
41
  constructor(options = {}) {
23
- this.options = (0, _getOptions.default)(options);
42
+ this.key = STYLELINT_PLUGIN;
43
+ this.options = (0, _options.getOptions)(options);
44
+ this.run = this.run.bind(this);
45
+ this.startTime = Date.now();
46
+ this.prevTimestamps = new Map();
24
47
  }
25
48
  /**
26
49
  * @param {Compiler} compiler
@@ -29,35 +52,112 @@ class StylelintWebpackPlugin {
29
52
 
30
53
 
31
54
  apply(compiler) {
32
- const options = { ...this.options,
33
- files: (0, _utils.parseFiles)(this.options.files, this.getContext(compiler))
34
- }; // eslint-disable-next-line
35
-
36
- const {
37
- lint
38
- } = require(options.stylelintPath);
39
-
40
- const {
41
- name: plugin
42
- } = this.constructor;
43
-
44
- if (options.lintDirtyModulesOnly) {
45
- const lintDirty = new _LintDirtyModulesPlugin.default(lint, compiler, options);
46
- /* istanbul ignore next */
47
-
48
- compiler.hooks.watchRun.tapAsync(plugin, (compilation, callback) => {
49
- lintDirty.apply(compilation, callback);
50
- });
51
- } else {
52
- compiler.hooks.run.tapAsync(plugin, (compilation, callback) => {
53
- (0, _linter.default)(lint, options, compilation, callback);
54
- });
55
- /* istanbul ignore next */
56
-
57
- compiler.hooks.watchRun.tapAsync(plugin, (compilation, callback) => {
58
- (0, _linter.default)(lint, options, compilation, callback);
59
- });
55
+ // Generate key for each compilation,
56
+ // this differentiates one from the other when being cached.
57
+ this.key = compiler.name || `${this.key}_${counter += 1}`; // If `lintDirtyModulesOnly` is disabled,
58
+ // execute the linter on the build
59
+
60
+ if (!this.options.lintDirtyModulesOnly) {
61
+ compiler.hooks.run.tapPromise(this.key, this.run);
60
62
  }
63
+
64
+ let isFirstRun = this.options.lintDirtyModulesOnly;
65
+ compiler.hooks.watchRun.tapPromise(this.key, c => {
66
+ if (isFirstRun) {
67
+ isFirstRun = false;
68
+ return Promise.resolve();
69
+ }
70
+
71
+ return this.run(c);
72
+ });
73
+ }
74
+ /**
75
+ * @param {Compiler} compiler
76
+ */
77
+
78
+
79
+ async run(compiler) {
80
+ // Do not re-hook
81
+
82
+ /* istanbul ignore if */
83
+ if ( // @ts-ignore
84
+ compiler.hooks.thisCompilation.taps.find(({
85
+ name
86
+ }) => name === this.key)) {
87
+ return;
88
+ }
89
+
90
+ const context = this.getContext(compiler);
91
+ const options = { ...this.options,
92
+ exclude: (0, _utils.parseFiles)(this.options.exclude || ['**/node_modules/**', compiler.options.output.path], context),
93
+ extensions: (0, _arrify.default)(this.options.extensions),
94
+ files: (0, _utils.parseFiles)(this.options.files || '', context)
95
+ };
96
+ const wanted = (0, _utils.parseFoldersToGlobs)(options.files, options.extensions);
97
+ const exclude = (0, _utils.parseFoldersToGlobs)(options.exclude);
98
+ compiler.hooks.thisCompilation.tap(this.key, compilation => {
99
+ /** @type {import('./linter').Linter} */
100
+ let lint;
101
+ /** @type {import('./linter').Reporter} */
102
+
103
+ let report;
104
+ /** @type number */
105
+
106
+ let threads;
107
+
108
+ try {
109
+ ({
110
+ lint,
111
+ report,
112
+ threads
113
+ } = (0, _linter.default)(this.key, options, compilation));
114
+ } catch (e) {
115
+ compilation.errors.push(e);
116
+ return;
117
+ }
118
+
119
+ compilation.hooks.finishModules.tap(this.key, () => {
120
+ const files = this.getFiles(compiler, wanted, exclude);
121
+
122
+ if (threads > 1) {
123
+ for (const file of files) {
124
+ lint((0, _utils.parseFiles)(file, context));
125
+ }
126
+ } else if (files.length > 0) {
127
+ lint((0, _utils.parseFiles)(files, context));
128
+ }
129
+ }); // await and interpret results
130
+
131
+ compilation.hooks.additionalAssets.tapPromise(this.key, processResults);
132
+
133
+ async function processResults() {
134
+ const {
135
+ errors,
136
+ warnings,
137
+ generateReportAsset
138
+ } = await report();
139
+
140
+ if (warnings && !options.failOnWarning) {
141
+ // @ts-ignore
142
+ compilation.warnings.push(warnings);
143
+ } else if (warnings && options.failOnWarning) {
144
+ // @ts-ignore
145
+ compilation.errors.push(warnings);
146
+ }
147
+
148
+ if (errors && options.failOnError) {
149
+ // @ts-ignore
150
+ compilation.errors.push(errors);
151
+ } else if (errors && !options.failOnError) {
152
+ // @ts-ignore
153
+ compilation.warnings.push(errors);
154
+ }
155
+
156
+ if (generateReportAsset) {
157
+ await generateReportAsset(compilation);
158
+ }
159
+ }
160
+ });
61
161
  }
62
162
  /**
63
163
  *
@@ -77,6 +177,88 @@ class StylelintWebpackPlugin {
77
177
 
78
178
  return this.options.context;
79
179
  }
180
+ /**
181
+ * @param {Compiler} compiler
182
+ * @param {string[]} wanted
183
+ * @param {string[]} exclude
184
+ * @returns {string[]}
185
+ */
186
+ // eslint-disable-next-line no-unused-vars
187
+
188
+
189
+ getFiles(compiler, wanted, exclude) {
190
+ // webpack 5
191
+ if (compiler.modifiedFiles) {
192
+ return Array.from(compiler.modifiedFiles).filter(file => (0, _micromatch.isMatch)(file, wanted, {
193
+ dot: true
194
+ }) && !(0, _micromatch.isMatch)(file, exclude, {
195
+ dot: true
196
+ }));
197
+ } // webpack 4
198
+
199
+ /* istanbul ignore next */
200
+
201
+
202
+ if (compiler.fileTimestamps && compiler.fileTimestamps.size > 0) {
203
+ return this.getChangedFiles(compiler.fileTimestamps).filter(file => (0, _micromatch.isMatch)(file, wanted, {
204
+ dot: true
205
+ }) && !(0, _micromatch.isMatch)(file, exclude, {
206
+ dot: true
207
+ }));
208
+ }
209
+
210
+ return _globby.default.sync(wanted, {
211
+ dot: true,
212
+ ignore: exclude
213
+ });
214
+ }
215
+ /**
216
+ * @param {Map<string, null | FileSystemInfoEntry | "ignore">} fileTimestamps
217
+ * @returns {string[]}
218
+ */
219
+
220
+ /* istanbul ignore next */
221
+
222
+
223
+ getChangedFiles(fileTimestamps) {
224
+ /**
225
+ * @param {null | FileSystemInfoEntry | "ignore"} fileSystemInfoEntry
226
+ * @returns {Partial<number>}
227
+ */
228
+ const getTimestamps = fileSystemInfoEntry => {
229
+ // @ts-ignore
230
+ if (fileSystemInfoEntry && fileSystemInfoEntry.timestamp) {
231
+ // @ts-ignore
232
+ return fileSystemInfoEntry.timestamp;
233
+ } // @ts-ignore
234
+
235
+
236
+ return fileSystemInfoEntry;
237
+ };
238
+ /**
239
+ * @param {string} filename
240
+ * @param {null | FileSystemInfoEntry | "ignore"} fileSystemInfoEntry
241
+ * @returns {boolean}
242
+ */
243
+
244
+
245
+ const hasFileChanged = (filename, fileSystemInfoEntry) => {
246
+ const prevTimestamp = getTimestamps(this.prevTimestamps.get(filename));
247
+ const timestamp = getTimestamps(fileSystemInfoEntry);
248
+ return (prevTimestamp || this.startTime) < (timestamp || Infinity);
249
+ };
250
+
251
+ const changedFiles = [];
252
+
253
+ for (const [filename, timestamp] of fileTimestamps.entries()) {
254
+ if (hasFileChanged(filename, timestamp)) {
255
+ changedFiles.push(filename);
256
+ }
257
+ }
258
+
259
+ this.prevTimestamps = fileTimestamps;
260
+ return changedFiles;
261
+ }
80
262
 
81
263
  }
82
264
 
package/dist/linter.js CHANGED
@@ -5,105 +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[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
- compilation.warnings.push(_StylelintError.default.format(options, warnings));
55
- warnings = [];
56
- }
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
+ */
57
131
 
58
- if (errors.length) {
59
- compilation.errors.push(_StylelintError.default.format(options, errors));
60
- 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;
61
165
  }
62
166
 
63
- next();
64
- });
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
+ }
65
175
 
66
- if (options.failOnError && errors.length) {
67
- callback(_StylelintError.default.format(options, errors));
68
- } else if (options.failOnWarning && warnings.length) {
69
- callback(_StylelintError.default.format(options, warnings));
70
- } else {
71
- callback();
176
+ await save(filePath, content);
72
177
  }
73
- }).catch(e => {
74
- compiler.hooks.afterEmit.tapAsync('StylelintWebpackPlugin', (compilation, next) => {
75
- compilation.errors.push(new _StylelintError.default(e.message));
76
- next();
77
- });
78
- callback();
79
- });
178
+ }
80
179
  }
81
180
  /**
82
- *
83
- * @param {Options} options
84
- * @param {Array<LintResult>} results
85
- * @returns {{errors: Array<LintResult>, warnings: Array<LintResult>}}
181
+ * @param {FormatterFunction} formatter
182
+ * @param {{ errors: LintResult[]; warnings: LintResult[]; }} results
183
+ * @returns {{errors?: StylelintError, warnings?: StylelintError}}
86
184
  */
87
185
 
88
186
 
89
- function parseResults(options, results) {
90
- /** @type {Array<LintResult>} */
91
- let errors = [];
92
- /** @type {Array<LintResult>} */
93
-
94
- let warnings = [];
95
-
96
- if (options.emitError) {
97
- errors = results.filter(file => fileHasErrors(file) || fileHasWarnings(file));
98
- } else if (options.emitWarning) {
99
- warnings = results.filter(file => fileHasErrors(file) || fileHasWarnings(file));
100
- } else {
101
- warnings = results.filter(file => !fileHasErrors(file) && fileHasWarnings(file));
102
- 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));
103
193
  }
104
194
 
105
- if (options.quiet && warnings.length) {
106
- warnings = [];
195
+ if (results.errors.length > 0) {
196
+ errors = new _StylelintError.default(formatter(results.errors));
107
197
  }
108
198
 
109
199
  return {
@@ -112,20 +202,99 @@ function parseResults(options, results) {
112
202
  };
113
203
  }
114
204
  /**
115
- * @param {LintResult} file
116
- * @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}
117
243
  */
118
244
 
119
245
 
120
- function fileHasErrors(file) {
121
- 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;
122
259
  }
123
260
  /**
124
- * @param {LintResult} file
125
- * @returns {boolean}
261
+ * @param {LintResult[]} results
262
+ * @returns {LintResult[]}
126
263
  */
127
264
 
128
265
 
129
- function fileHasWarnings(file) {
130
- 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;
131
300
  }