UNPKG

14.7 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3/* --------------------------------------------------------------------------------------------
4 * Copyright (c) Remy Suen. All rights reserved.
5 * Licensed under the MIT License. See License.txt in the project root for license information.
6 * ------------------------------------------------------------------------------------------ */
7const vscode_languageserver_types_1 = require("vscode-languageserver-types");
8const util_1 = require("./util");
9class Property {
10 constructor(document, escapeChar, arg, arg2) {
11 this.assignmentOperatorRange = null;
12 this.assignmentOperator = null;
13 this.valueRange = null;
14 this.value = null;
15 this.document = document;
16 this.escapeChar = escapeChar;
17 this.nameRange = Property.getNameRange(document, arg);
18 let value = document.getText().substring(document.offsetAt(this.nameRange.start), document.offsetAt(this.nameRange.end));
19 this.name = Property.getValue(value, escapeChar);
20 if (arg2) {
21 this.valueRange = arg2.getRange();
22 value = document.getText().substring(document.offsetAt(this.valueRange.start), document.offsetAt(this.valueRange.end));
23 this.value = Property.getValue(value, escapeChar);
24 this.range = vscode_languageserver_types_1.Range.create(this.nameRange.start, this.valueRange.end);
25 }
26 else {
27 let argRange = arg.getRange();
28 if (this.nameRange.start.line === argRange.start.line
29 && this.nameRange.start.character === argRange.start.character
30 && this.nameRange.end.line === argRange.end.line
31 && this.nameRange.end.character === argRange.end.character) {
32 }
33 else {
34 this.valueRange = Property.getValueRange(document, arg);
35 value = document.getText().substring(document.offsetAt(this.valueRange.start), document.offsetAt(this.valueRange.end));
36 this.value = Property.getValue(value, escapeChar);
37 this.assignmentOperatorRange = vscode_languageserver_types_1.Range.create(this.nameRange.end, this.valueRange.start);
38 this.assignmentOperator = "=";
39 }
40 this.range = argRange;
41 }
42 }
43 getRange() {
44 return this.range;
45 }
46 getName() {
47 return this.name;
48 }
49 getNameRange() {
50 return this.nameRange;
51 }
52 getValue() {
53 return this.value;
54 }
55 getValueRange() {
56 return this.valueRange;
57 }
58 /**
59 * Retrieves the operator used for delimiting between the name and
60 * value of this property. This will either be the "=" character
61 * or null if a character was not used or if this property has no
62 * value defined.
63 */
64 getAssignmentOperator() {
65 return this.assignmentOperator;
66 }
67 getAssignmentOperatorRange() {
68 return this.assignmentOperatorRange;
69 }
70 /**
71 * Returns the value of this property including any enclosing
72 * single or double quotes and relevant escape characters.
73 * Escaped newlines and its associated contiguous whitespace
74 * characters however will not be returned as they are deemed to
75 * be uninteresting to clients trying to return a Dockerfile.
76 *
77 * @return the unescaped value of this property or null if this
78 * property has no associated value
79 */
80 getUnescapedValue() {
81 if (this.valueRange === null) {
82 return null;
83 }
84 let escaped = false;
85 let rawValue = "";
86 let value = this.document.getText().substring(this.document.offsetAt(this.valueRange.start), this.document.offsetAt(this.valueRange.end));
87 rawLoop: for (let i = 0; i < value.length; i++) {
88 let char = value.charAt(i);
89 switch (char) {
90 case this.escapeChar:
91 for (let j = i + 1; j < value.length; j++) {
92 switch (value.charAt(j)) {
93 case '\r':
94 j++;
95 case '\n':
96 escaped = true;
97 i = j;
98 continue rawLoop;
99 case ' ':
100 case '\t':
101 break;
102 default:
103 rawValue = rawValue + char;
104 continue rawLoop;
105 }
106 }
107 // this happens if there's only whitespace after the escape character
108 rawValue = rawValue + char;
109 break;
110 case '\r':
111 case '\n':
112 break;
113 case ' ':
114 case '\t':
115 if (!escaped) {
116 rawValue = rawValue + char;
117 }
118 break;
119 case '#':
120 if (escaped) {
121 for (let j = i + 1; j < value.length; j++) {
122 switch (value.charAt(j)) {
123 case '\r':
124 j++;
125 case '\n':
126 i = j;
127 continue rawLoop;
128 }
129 }
130 }
131 else {
132 rawValue = rawValue + char;
133 }
134 break;
135 default:
136 rawValue = rawValue + char;
137 escaped = false;
138 break;
139 }
140 }
141 return rawValue;
142 }
143 static getNameRange(document, arg) {
144 let value = arg.getValue();
145 let index = value.indexOf('=');
146 if (index !== -1) {
147 let initial = value.charAt(0);
148 let before = value.charAt(index - 1);
149 // check if content before the equals sign are in quotes
150 // "var"=value
151 // 'var'=value
152 // otherwise, just assume it's a standard definition
153 // var=value
154 if ((initial === '"' && before === '"') || (initial === '\'' && before === '\'') || (initial !== '"' && initial !== '\'')) {
155 return vscode_languageserver_types_1.Range.create(arg.getRange().start, document.positionAt(document.offsetAt(arg.getRange().start) + index));
156 }
157 }
158 // no '=' found, just defined the property's name
159 return arg.getRange();
160 }
161 static getValueRange(document, arg) {
162 return vscode_languageserver_types_1.Range.create(document.positionAt(document.offsetAt(arg.getRange().start) + arg.getValue().indexOf('=') + 1), document.positionAt(document.offsetAt(arg.getRange().end)));
163 }
164 /**
165 * Returns the actual value of this key-value pair. The value will
166 * have its escape characters removed if applicable. If the value
167 * spans multiple lines and there are comments nested within the
168 * lines, they too will be removed.
169 *
170 * @return the value that this key-value pair will actually be, may
171 * be null if no value is defined, may be the empty string
172 * if the value only consists of whitespace
173 */
174 static getValue(value, escapeChar) {
175 let escaped = false;
176 const skip = util_1.Util.findLeadingNonWhitespace(value, escapeChar);
177 if (skip !== 0 && value.charAt(skip) === '#') {
178 // need to skip over comments
179 escaped = true;
180 }
181 value = value.substring(skip);
182 let first = value.charAt(0);
183 let last = value.charAt(value.length - 1);
184 let literal = first === '\'' || first === '"';
185 let inSingle = (first === '\'' && last === '\'');
186 let inDouble = false;
187 if (first === '"') {
188 for (let i = 1; i < value.length; i++) {
189 if (value.charAt(i) === escapeChar) {
190 i++;
191 }
192 else if (value.charAt(i) === '"' && i === value.length - 1) {
193 inDouble = true;
194 }
195 }
196 }
197 if (inSingle || inDouble) {
198 value = value.substring(1, value.length - 1);
199 }
200 let commentCheck = -1;
201 let escapedValue = "";
202 let start = 0;
203 parseValue: for (let i = 0; i < value.length; i++) {
204 let char = value.charAt(i);
205 switch (char) {
206 case escapeChar:
207 if (i + 1 === value.length) {
208 escapedValue = escapedValue + escapeChar;
209 break parseValue;
210 }
211 char = value.charAt(i + 1);
212 if (char === ' ' || char === '\t') {
213 whitespaceCheck: for (let j = i + 2; j < value.length; j++) {
214 let char2 = value.charAt(j);
215 switch (char2) {
216 case ' ':
217 case '\t':
218 break;
219 case '\r':
220 j++;
221 case '\n':
222 escaped = true;
223 i = j;
224 continue parseValue;
225 default:
226 if (!inDouble && !inSingle && !literal) {
227 if (char2 === escapeChar) {
228 // add the escaped character
229 escapedValue = escapedValue + char;
230 // now start parsing from the next escape character
231 i = i + 1;
232 }
233 else {
234 // the expectation is that this j = i + 2 here
235 escapedValue = escapedValue + char + char2;
236 i = j;
237 }
238 continue parseValue;
239 }
240 break whitespaceCheck;
241 }
242 }
243 }
244 if (inDouble) {
245 if (char === '\r') {
246 escaped = true;
247 i = i + 2;
248 }
249 else if (char === '\n') {
250 escaped = true;
251 i++;
252 }
253 else if (char !== '"') {
254 if (char === escapeChar) {
255 i++;
256 }
257 escapedValue = escapedValue + escapeChar;
258 }
259 continue parseValue;
260 }
261 else if (inSingle || literal) {
262 if (char === '\r') {
263 escaped = true;
264 i = i + 2;
265 }
266 else if (char === '\n') {
267 escaped = true;
268 i++;
269 }
270 else {
271 escapedValue = escapedValue + escapeChar;
272 }
273 continue parseValue;
274 }
275 else if (char === escapeChar) {
276 // double escape, append one and move on
277 escapedValue = escapedValue + escapeChar;
278 i++;
279 }
280 else if (char === '\r') {
281 escaped = true;
282 // offset one more for \r\n
283 i = i + 2;
284 }
285 else if (char === '\n') {
286 escaped = true;
287 i++;
288 start = i;
289 }
290 else {
291 // any other escapes are simply ignored
292 escapedValue = escapedValue + char;
293 i++;
294 }
295 break;
296 case ' ':
297 case '\t':
298 if (escaped && commentCheck === -1) {
299 commentCheck = i;
300 }
301 escapedValue = escapedValue + char;
302 break;
303 case '\r':
304 i++;
305 case '\n':
306 if (escaped && commentCheck !== -1) {
307 // rollback and remove the whitespace that was previously appended
308 escapedValue = escapedValue.substring(0, escapedValue.length - (i - commentCheck - 1));
309 commentCheck = -1;
310 }
311 break;
312 case '#':
313 // a newline was escaped and now there's a comment
314 if (escaped) {
315 if (commentCheck !== -1) {
316 // rollback and remove the whitespace that was previously appended
317 escapedValue = escapedValue.substring(0, escapedValue.length - (i - commentCheck));
318 commentCheck = -1;
319 }
320 newlineCheck: for (let j = i + 1; j < value.length; j++) {
321 switch (value.charAt(j)) {
322 case '\r':
323 j++;
324 case '\n':
325 i = j;
326 break newlineCheck;
327 }
328 }
329 continue parseValue;
330 }
331 default:
332 if (escaped) {
333 escaped = false;
334 commentCheck = -1;
335 }
336 escapedValue = escapedValue + char;
337 break;
338 }
339 }
340 return escapedValue;
341 }
342}
343exports.Property = Property;