UNPKG

10.3 kBJavaScriptView Raw
1"use strict";
2/**
3 * @license
4 * Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
5 * This code may only be used under the BSD style license found at
6 * http://polymer.github.io/LICENSE.txt
7 * The complete set of authors may be found at
8 * http://polymer.github.io/AUTHORS.txt
9 * The complete set of contributors may be found at
10 * http://polymer.github.io/CONTRIBUTORS.txt
11 * Code distributed by Google as part of the polymer project is also
12 * subject to an additional IP rights grant found at
13 * http://polymer.github.io/PATENTS.txt
14 */
15var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
16 return new (P || (P = Promise))(function (resolve, reject) {
17 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
18 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
19 function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
20 step((generator = generator.apply(thisArg, _arguments || [])).next());
21 });
22};
23Object.defineProperty(exports, "__esModule", { value: true });
24const chalk = require("chalk");
25const code_printer_1 = require("../warning/code-printer");
26const source_range_1 = require("./source-range");
27const stable = require('stable');
28class Warning {
29 constructor(init) {
30 /**
31 * Other actions that could be taken in response to this warning.
32 *
33 * Each action is separate and they may be mutually exclusive. In the case
34 * of edit actions they often are.
35 */
36 this.actions = undefined;
37 ({
38 message: this.message,
39 sourceRange: this.sourceRange,
40 severity: this.severity,
41 code: this.code,
42 parsedDocument: this._parsedDocument
43 } = init);
44 this.fix = init.fix;
45 this.actions = init.actions;
46 if (!this.sourceRange) {
47 throw new Error(`Attempted to construct a ${this.code} ` +
48 `warning without a source range.`);
49 }
50 if (!this._parsedDocument) {
51 throw new Error(`Attempted to construct a ${this.code} ` +
52 `warning without a parsed document.`);
53 }
54 }
55 toString(options = {}) {
56 const opts = Object.assign({}, defaultPrinterOptions, options);
57 const colorize = opts.color ? this._severityToColorFunction(this.severity) :
58 (s) => s;
59 const severity = this._severityToString(colorize);
60 let result = '';
61 if (options.verbosity !== 'one-line') {
62 const underlined = code_printer_1.underlineCode(this.sourceRange, this._parsedDocument, colorize, options.maxCodeLines);
63 if (underlined) {
64 result += underlined;
65 }
66 if (options.verbosity === 'code-only') {
67 return result;
68 }
69 result += '\n\n';
70 }
71 let file = this.sourceRange.file;
72 if (opts.resolver) {
73 file = opts.resolver.relative(this.sourceRange.file);
74 }
75 result += `${file}(${this.sourceRange.start.line + 1},${this.sourceRange.start.column +
76 1}) ${severity} [${this.code}] - ${this.message}\n`;
77 return result;
78 }
79 _severityToColorFunction(severity) {
80 switch (severity) {
81 case Severity.ERROR:
82 return chalk.red;
83 case Severity.WARNING:
84 return chalk.yellow;
85 case Severity.INFO:
86 return chalk.green;
87 default:
88 const never = severity;
89 throw new Error(`Unknown severity value - ${never}` +
90 ` - encountered while printing warning.`);
91 }
92 }
93 _severityToString(colorize) {
94 switch (this.severity) {
95 case Severity.ERROR:
96 return colorize('error');
97 case Severity.WARNING:
98 return colorize('warning');
99 case Severity.INFO:
100 return colorize('info');
101 default:
102 const never = this.severity;
103 throw new Error(`Unknown severity value - ${never} - ` +
104 `encountered while printing warning.`);
105 }
106 }
107 toJSON() {
108 return {
109 code: this.code,
110 message: this.message,
111 severity: this.severity,
112 sourceRange: this.sourceRange,
113 };
114 }
115}
116exports.Warning = Warning;
117var Severity;
118(function (Severity) {
119 Severity[Severity["ERROR"] = 0] = "ERROR";
120 Severity[Severity["WARNING"] = 1] = "WARNING";
121 Severity[Severity["INFO"] = 2] = "INFO";
122})(Severity = exports.Severity || (exports.Severity = {}));
123// TODO(rictic): can we get rid of this class entirely?
124class WarningCarryingException extends Error {
125 constructor(warning) {
126 super(warning.message);
127 this.warning = warning;
128 }
129}
130exports.WarningCarryingException = WarningCarryingException;
131const defaultPrinterOptions = {
132 verbosity: 'full',
133 color: true
134};
135/**
136 * Takes the given edits and, provided there are no overlaps, applies them to
137 * the contents loadable from the given loader.
138 *
139 * If there are overlapping edits, then edits earlier in the array get priority
140 * over later ones.
141 */
142function applyEdits(edits, loader) {
143 return __awaiter(this, void 0, void 0, function* () {
144 const result = {
145 appliedEdits: [],
146 incompatibleEdits: [],
147 editedFiles: new Map()
148 };
149 const replacementsByFile = new Map();
150 for (const edit of edits) {
151 if (canApply(edit, replacementsByFile)) {
152 result.appliedEdits.push(edit);
153 }
154 else {
155 result.incompatibleEdits.push(edit);
156 }
157 }
158 for (const [file, replacements] of replacementsByFile) {
159 const document = yield loader(file);
160 let contents = document.contents;
161 /**
162 * This is the important bit. We know that none of the replacements overlap,
163 * so in order for their source ranges in the file to all be valid at the
164 * time we apply them, we simply need to apply them starting from the end
165 * of the document and working backwards to the beginning.
166 *
167 * To preserve ordering of insertions to the same position, we use a stable
168 * sort.
169 */
170 stable.inplace(replacements, (a, b) => {
171 const leftEdgeComp = source_range_1.comparePositionAndRange(b.range.start, a.range, true);
172 if (leftEdgeComp !== 0) {
173 return leftEdgeComp;
174 }
175 return source_range_1.comparePositionAndRange(b.range.end, a.range, false);
176 });
177 for (const replacement of replacements) {
178 const offsets = document.sourceRangeToOffsets(replacement.range);
179 contents = contents.slice(0, offsets[0]) + replacement.replacementText +
180 contents.slice(offsets[1]);
181 }
182 result.editedFiles.set(file, contents);
183 }
184 return result;
185 });
186}
187exports.applyEdits = applyEdits;
188/**
189 * We can apply an edit if none of its replacements overlap with any accepted
190 * replacement.
191 */
192function canApply(edit, replacements) {
193 for (let i = 0; i < edit.length; i++) {
194 const replacement = edit[i];
195 const fileLocalReplacements = replacements.get(replacement.range.file) || [];
196 // TODO(rictic): binary search
197 for (const acceptedReplacement of fileLocalReplacements) {
198 if (!areReplacementsCompatible(replacement, acceptedReplacement)) {
199 return false;
200 }
201 }
202 // Also check consistency between multiple replacements in this edit.
203 for (let j = 0; j < i; j++) {
204 const acceptedReplacement = edit[j];
205 if (!areReplacementsCompatible(replacement, acceptedReplacement)) {
206 return false;
207 }
208 }
209 }
210 // Ok, we can be applied to the replacements, so add our replacements in.
211 for (const replacement of edit) {
212 if (!replacements.has(replacement.range.file)) {
213 replacements.set(replacement.range.file, [replacement]);
214 }
215 else {
216 const fileReplacements = replacements.get(replacement.range.file);
217 fileReplacements.push(replacement);
218 }
219 }
220 return true;
221}
222function areReplacementsCompatible(a, b) {
223 if (a.range.file !== b.range.file) {
224 return true;
225 }
226 if (areRangesEqual(a.range, b.range)) {
227 // Equal ranges are compatible if the ranges are empty (i.e. the edit is an
228 // insertion, not a replacement).
229 return (a.range.start.column === a.range.end.column &&
230 a.range.start.line === a.range.end.line);
231 }
232 return !(source_range_1.isPositionInsideRange(a.range.start, b.range, false) ||
233 source_range_1.isPositionInsideRange(a.range.end, b.range, false) ||
234 source_range_1.isPositionInsideRange(b.range.start, a.range, false) ||
235 source_range_1.isPositionInsideRange(b.range.end, a.range, false));
236}
237function areRangesEqual(a, b) {
238 return a.start.line === b.start.line && a.start.column === b.start.column &&
239 a.end.line === b.end.line && a.end.column === b.end.column;
240}
241function makeParseLoader(analyzer, analysis) {
242 return (url) => __awaiter(this, void 0, void 0, function* () {
243 if (analysis) {
244 const cachedResult = analysis.getDocument(url);
245 if (cachedResult.successful) {
246 return cachedResult.value.parsedDocument;
247 }
248 }
249 const result = (yield analyzer.analyze([url])).getDocument(url);
250 if (result.successful) {
251 return result.value.parsedDocument;
252 }
253 let message = '';
254 if (result.error) {
255 message = result.error.message;
256 }
257 throw new Error(`Cannot load file at: ${JSON.stringify(url)}: ${message}`);
258 });
259}
260exports.makeParseLoader = makeParseLoader;
261//# sourceMappingURL=warning.js.map
\No newline at end of file