1 | import postCss from 'postcss';
|
2 | import * as path from 'path';
|
3 |
|
4 | function loadDiagnostic(context, postcssError, filePath) {
|
5 | if (!postcssError || !context) {
|
6 | return;
|
7 | }
|
8 | const level = postcssError.level === 'warning' ? 'warn' : postcssError.level || 'error';
|
9 | const diagnostic = {
|
10 | level,
|
11 | type: 'css',
|
12 | language: 'postcss',
|
13 | header: `postcss ${level}`,
|
14 | code: postcssError.status && postcssError.status.toString(),
|
15 | relFilePath: null,
|
16 | absFilePath: null,
|
17 | messageText: postcssError.reason || postcssError.message || JSON.stringify(postcssError),
|
18 | lines: [],
|
19 | };
|
20 | if (filePath) {
|
21 | diagnostic.absFilePath = filePath;
|
22 | diagnostic.relFilePath = formatFileName(context.config.rootDir, diagnostic.absFilePath);
|
23 | diagnostic.header = formatHeader('postcss', diagnostic.absFilePath, context.config.rootDir, postcssError.line);
|
24 | if (postcssError.line > -1) {
|
25 | try {
|
26 | const sourceText = context.fs.readFileSync(diagnostic.absFilePath);
|
27 | const srcLines = sourceText.split(/(\r?\n)/);
|
28 | const errorLine = {
|
29 | lineIndex: postcssError.line - 1,
|
30 | lineNumber: postcssError.line,
|
31 | text: srcLines[postcssError.line - 1],
|
32 | errorCharStart: postcssError.column,
|
33 | errorLength: 0,
|
34 | };
|
35 | for (let i = errorLine.errorCharStart; i >= 0; i--) {
|
36 | if (STOP_CHARS.indexOf(errorLine.text.charAt(i)) > -1) {
|
37 | break;
|
38 | }
|
39 | errorLine.errorCharStart = i;
|
40 | }
|
41 | for (let j = errorLine.errorCharStart; j <= errorLine.text.length; j++) {
|
42 | if (STOP_CHARS.indexOf(errorLine.text.charAt(j)) > -1) {
|
43 | break;
|
44 | }
|
45 | errorLine.errorLength++;
|
46 | }
|
47 | if (errorLine.errorLength === 0 && errorLine.errorCharStart > 0) {
|
48 | errorLine.errorLength = 1;
|
49 | errorLine.errorCharStart--;
|
50 | }
|
51 | diagnostic.lines.push(errorLine);
|
52 | if (errorLine.lineIndex > 0) {
|
53 | const previousLine = {
|
54 | lineIndex: errorLine.lineIndex - 1,
|
55 | lineNumber: errorLine.lineNumber - 1,
|
56 | text: srcLines[errorLine.lineIndex - 1],
|
57 | errorCharStart: -1,
|
58 | errorLength: -1,
|
59 | };
|
60 | diagnostic.lines.unshift(previousLine);
|
61 | }
|
62 | if (errorLine.lineIndex + 1 < srcLines.length) {
|
63 | const nextLine = {
|
64 | lineIndex: errorLine.lineIndex + 1,
|
65 | lineNumber: errorLine.lineNumber + 1,
|
66 | text: srcLines[errorLine.lineIndex + 1],
|
67 | errorCharStart: -1,
|
68 | errorLength: -1,
|
69 | };
|
70 | diagnostic.lines.push(nextLine);
|
71 | }
|
72 | }
|
73 | catch (e) {
|
74 | console.error(`StylePostcssPlugin loadDiagnostic, ${e}`);
|
75 | }
|
76 | }
|
77 | }
|
78 | context.diagnostics.push(diagnostic);
|
79 | }
|
80 | function formatFileName(rootDir, fileName) {
|
81 | if (!rootDir || !fileName)
|
82 | return '';
|
83 | fileName = fileName.replace(rootDir, '');
|
84 | if (/\/|\\/.test(fileName.charAt(0))) {
|
85 | fileName = fileName.substr(1);
|
86 | }
|
87 | if (fileName.length > 80) {
|
88 | fileName = '...' + fileName.substr(fileName.length - 80);
|
89 | }
|
90 | return fileName;
|
91 | }
|
92 | function formatHeader(type, fileName, rootDir, startLineNumber = null, endLineNumber = null) {
|
93 | let header = `${type}: ${formatFileName(rootDir, fileName)}`;
|
94 | if (startLineNumber !== null && startLineNumber > 0) {
|
95 | if (endLineNumber !== null && endLineNumber > startLineNumber) {
|
96 | header += `, lines: ${startLineNumber} - ${endLineNumber}`;
|
97 | }
|
98 | else {
|
99 | header += `, line: ${startLineNumber}`;
|
100 | }
|
101 | }
|
102 | return header;
|
103 | }
|
104 | const STOP_CHARS = [
|
105 | '',
|
106 | '\n',
|
107 | '\r',
|
108 | '\t',
|
109 | ' ',
|
110 | ':',
|
111 | ';',
|
112 | ',',
|
113 | '{',
|
114 | '}',
|
115 | '.',
|
116 | '#',
|
117 | '@',
|
118 | '!',
|
119 | '[',
|
120 | ']',
|
121 | '(',
|
122 | ')',
|
123 | '&',
|
124 | '+',
|
125 | '~',
|
126 | '^',
|
127 | '*',
|
128 | '$',
|
129 | ];
|
130 |
|
131 | function usePlugin(fileName) {
|
132 | return /(\.css|\.pcss|\.postcss)$/i.test(fileName);
|
133 | }
|
134 | function getRenderOptions(opts, sourceText, context) {
|
135 | const renderOpts = {
|
136 | plugins: opts.plugins || [],
|
137 | };
|
138 |
|
139 | renderOpts.data = sourceText || '';
|
140 | const injectGlobalPaths = Array.isArray(opts.injectGlobalPaths) ? opts.injectGlobalPaths.slice() : [];
|
141 | if (injectGlobalPaths.length > 0) {
|
142 |
|
143 | const injectText = injectGlobalPaths
|
144 | .map((injectGlobalPath) => {
|
145 | if (!path.isAbsolute(injectGlobalPath)) {
|
146 |
|
147 | injectGlobalPath = path.join(context.config.rootDir, injectGlobalPath);
|
148 | }
|
149 | return `@import "${injectGlobalPath}";`;
|
150 | })
|
151 | .join('');
|
152 | renderOpts.data = injectText + renderOpts.data;
|
153 | }
|
154 | return renderOpts;
|
155 | }
|
156 | function createResultsId(fileName) {
|
157 |
|
158 | const pathParts = fileName.split('.');
|
159 | pathParts[pathParts.length - 1] = 'css';
|
160 | return pathParts.join('.');
|
161 | }
|
162 |
|
163 | function postcss(opts = {}) {
|
164 | return {
|
165 | name: 'postcss',
|
166 | pluginType: 'css',
|
167 | transform(sourceText, fileName, context) {
|
168 | if (!opts.hasOwnProperty('plugins') || opts.plugins.length === 0) {
|
169 | return null;
|
170 | }
|
171 | if (!context || !usePlugin(fileName)) {
|
172 | return null;
|
173 | }
|
174 | const renderOpts = getRenderOptions(opts, sourceText, context);
|
175 | const results = {
|
176 | id: createResultsId(fileName),
|
177 | };
|
178 | if (sourceText.trim() === '') {
|
179 | results.code = '';
|
180 | return Promise.resolve(results);
|
181 | }
|
182 | return new Promise((resolve) => {
|
183 | postCss(renderOpts.plugins)
|
184 | .process(renderOpts.data, {
|
185 | from: fileName,
|
186 | })
|
187 | .then((postCssResults) => {
|
188 | const warnings = postCssResults.warnings();
|
189 | if (warnings.length > 0) {
|
190 |
|
191 | warnings.forEach((warn) => {
|
192 | const err = {
|
193 | reason: warn.text,
|
194 | level: warn.type,
|
195 | column: warn.column || -1,
|
196 | line: warn.line || -1,
|
197 | };
|
198 | loadDiagnostic(context, err, fileName);
|
199 | });
|
200 | const mappedWarnings = warnings
|
201 | .map((warn) => {
|
202 | return `${warn.type} ${warn.plugin ? `(${warn.plugin})` : ''}: ${warn.text}`;
|
203 | })
|
204 | .join(', ');
|
205 | results.code = `/** postcss ${mappedWarnings} **/`;
|
206 | resolve(results);
|
207 | }
|
208 | else {
|
209 | results.code = postCssResults.css.toString();
|
210 | results.dependencies = postCssResults.messages
|
211 | .filter((message) => message.type === 'dependency')
|
212 | .map((dependency) => dependency.file);
|
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 | context.fs.writeFile(results.id, results.code, { inMemoryOnly: true }).then(() => {
|
225 | resolve(results);
|
226 | });
|
227 | }
|
228 | return results;
|
229 | })
|
230 | .catch((err) => {
|
231 | loadDiagnostic(context, err, fileName);
|
232 | results.code = `/** postcss error${err && err.message ? ': ' + err.message : ''} **/`;
|
233 | resolve(results);
|
234 | });
|
235 | });
|
236 | },
|
237 | };
|
238 | }
|
239 |
|
240 | export { postcss };
|