UNPKG

16.9 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 instruction_1 = require("./instruction");
9const property_1 = require("./property");
10const argument_1 = require("./argument");
11const util_1 = require("./util");
12class PropertyInstruction extends instruction_1.Instruction {
13 constructor(document, range, dockerfile, escapeChar, instruction, instructionRange) {
14 super(document, range, dockerfile, escapeChar, instruction, instructionRange);
15 this.properties = undefined;
16 }
17 getProperties() {
18 if (this.properties === undefined) {
19 let args = this.getPropertyArguments();
20 if (args.length === 0) {
21 this.properties = [];
22 }
23 else if (args.length === 1) {
24 this.properties = [new property_1.Property(this.document, this.escapeChar, args[0])];
25 }
26 else if (args.length === 2) {
27 if (args[0].getValue().indexOf('=') === -1) {
28 this.properties = [new property_1.Property(this.document, this.escapeChar, args[0], args[1])];
29 }
30 else {
31 this.properties = [
32 new property_1.Property(this.document, this.escapeChar, args[0]),
33 new property_1.Property(this.document, this.escapeChar, args[1])
34 ];
35 }
36 }
37 else if (args[0].getValue().indexOf('=') === -1) {
38 let text = this.document.getText();
39 let start = args[1].getRange().start;
40 let end = args[args.length - 1].getRange().end;
41 text = text.substring(this.document.offsetAt(start), this.document.offsetAt(end));
42 this.properties = [new property_1.Property(this.document, this.escapeChar, args[0], new argument_1.Argument(text, vscode_languageserver_types_1.Range.create(args[1].getRange().start, args[args.length - 1].getRange().end)))];
43 }
44 else {
45 this.properties = [];
46 for (let i = 0; i < args.length; i++) {
47 this.properties.push(new property_1.Property(this.document, this.escapeChar, args[i]));
48 }
49 }
50 }
51 return this.properties;
52 }
53 /**
54 * Goes from the back of the string and returns the first
55 * non-whitespace character that is found. If an escape character
56 * is found with newline characters, the escape character will
57 * not be considered a non-whitespace character and its index in
58 * the string will not be returned.
59 *
60 * @param content the string to search through
61 * @return the index in the string for the first non-whitespace
62 * character when searching from the end of the string
63 */
64 findTrailingNonWhitespace(content) {
65 // loop back to find the first non-whitespace character
66 let index = content.length;
67 whitespaceCheck: for (let i = content.length - 1; i >= 0; i--) {
68 switch (content.charAt(i)) {
69 case ' ':
70 case '\t':
71 continue;
72 case '\n':
73 if (content.charAt(i - 1) === '\r') {
74 i = i - 1;
75 }
76 case '\r':
77 newlineCheck: for (let j = i - 1; j >= 0; j--) {
78 switch (content.charAt(j)) {
79 case ' ':
80 case '\t':
81 case '\r':
82 case '\n':
83 case this.escapeChar:
84 continue;
85 default:
86 index = j;
87 break newlineCheck;
88 }
89 }
90 break whitespaceCheck;
91 default:
92 index = i;
93 break whitespaceCheck;
94 }
95 }
96 return index;
97 }
98 getPropertyArguments() {
99 const args = [];
100 let range = this.getInstructionRange();
101 let instructionNameEndOffset = this.document.offsetAt(range.end);
102 let extra = instructionNameEndOffset - this.document.offsetAt(range.start);
103 let content = this.getTextContent();
104 let fullArgs = content.substring(extra);
105 let start = util_1.Util.findLeadingNonWhitespace(fullArgs, this.escapeChar);
106 if (start === -1) {
107 // only whitespace found, no arguments
108 return [];
109 }
110 const startPosition = this.document.positionAt(instructionNameEndOffset + start);
111 // records whether the parser has just processed an escaped newline or not,
112 // if our starting position is not on the same line as the instruction then
113 // the start of the content is already on an escaped line
114 let escaped = range.start.line !== startPosition.line;
115 // flag to track if the last character was an escape character
116 let endingEscape = false;
117 // position before the first escape character was hit
118 let mark = -1;
119 let end = this.findTrailingNonWhitespace(fullArgs);
120 content = fullArgs.substring(start, end + 1);
121 let argStart = escaped ? -1 : 0;
122 let spaced = false;
123 argumentLoop: for (let i = 0; i < content.length; i++) {
124 let char = content.charAt(i);
125 switch (char) {
126 case this.escapeChar:
127 if (i + 1 === content.length) {
128 endingEscape = true;
129 break argumentLoop;
130 }
131 if (!escaped) {
132 mark = i;
133 }
134 switch (content.charAt(i + 1)) {
135 case ' ':
136 case '\t':
137 if (!util_1.Util.isWhitespace(content.charAt(i + 2))) {
138 // space was escaped, continue as normal
139 i = i + 1;
140 continue argumentLoop;
141 }
142 // whitespace encountered, need to figure out if it extends to EOL
143 whitespaceCheck: for (let j = i + 2; j < content.length; j++) {
144 switch (content.charAt(j)) {
145 case '\r':
146 // offset one more for \r\n
147 j++;
148 case '\n':
149 // whitespace only, safe to skip
150 escaped = true;
151 i = j;
152 continue argumentLoop;
153 case ' ':
154 case '\t':
155 // ignore whitespace
156 break;
157 default:
158 // whitespace doesn't extend to EOL, create an argument
159 args.push(new argument_1.Argument(content.substring(argStart, i), vscode_languageserver_types_1.Range.create(this.document.positionAt(instructionNameEndOffset + start + argStart), this.document.positionAt(instructionNameEndOffset + start + i + 2))));
160 argStart = j;
161 break whitespaceCheck;
162 }
163 }
164 // go back and start processing the encountered non-whitespace character
165 i = argStart - 1;
166 continue argumentLoop;
167 case '\r':
168 // offset one more for \r\n
169 i++;
170 case '\n':
171 // immediately followed by a newline, skip the newline
172 escaped = true;
173 i = i + 1;
174 continue argumentLoop;
175 case this.escapeChar:
176 // double escape found, skip it and move on
177 if (argStart === -1) {
178 argStart = i;
179 }
180 i = i + 1;
181 continue argumentLoop;
182 default:
183 if (argStart === -1) {
184 argStart = i;
185 }
186 // non-whitespace encountered, skip the escape and process the
187 // character normally
188 continue argumentLoop;
189 }
190 case '\'':
191 case '"':
192 if (spaced) {
193 this.createSpacedArgument(argStart, args, content, mark, instructionNameEndOffset, start);
194 // reset to start a new argument
195 argStart = i;
196 spaced = false;
197 }
198 if (argStart === -1) {
199 argStart = i;
200 }
201 for (let j = i + 1; j < content.length; j++) {
202 switch (content.charAt(j)) {
203 case char:
204 if (content.charAt(j + 1) !== ' ' && content.charAt(j + 1) !== '') {
205 // there is more content after this quote,
206 // continue so that it is all processed as
207 // one single argument
208 i = j;
209 continue argumentLoop;
210 }
211 args.push(new argument_1.Argument(content.substring(argStart, j + 1), vscode_languageserver_types_1.Range.create(this.document.positionAt(instructionNameEndOffset + start + argStart), this.document.positionAt(instructionNameEndOffset + start + j + 1))));
212 i = j;
213 argStart = -1;
214 continue argumentLoop;
215 case this.escapeChar:
216 j++;
217 break;
218 }
219 }
220 break argumentLoop;
221 case ' ':
222 case '\t':
223 if (escaped) {
224 // consider there to be a space only if an argument
225 // is not spanning multiple lines
226 if (argStart !== -1) {
227 spaced = true;
228 }
229 }
230 else if (argStart !== -1) {
231 args.push(new argument_1.Argument(content.substring(argStart, i), vscode_languageserver_types_1.Range.create(this.document.positionAt(instructionNameEndOffset + start + argStart), this.document.positionAt(instructionNameEndOffset + start + i))));
232 argStart = -1;
233 }
234 break;
235 case '\r':
236 // offset one more for \r\n
237 i++;
238 case '\n':
239 spaced = false;
240 break;
241 case '#':
242 if (escaped) {
243 // a newline was escaped and now there's a comment
244 for (let j = i + 1; j < content.length; j++) {
245 switch (content.charAt(j)) {
246 case '\r':
247 j++;
248 case '\n':
249 i = j;
250 spaced = false;
251 continue argumentLoop;
252 }
253 }
254 // went to the end without finding a newline,
255 // the comment was the last line in the instruction,
256 // just stop parsing, create an argument if needed
257 if (argStart !== -1) {
258 let value = content.substring(argStart, mark);
259 args.push(new argument_1.Argument(value, vscode_languageserver_types_1.Range.create(this.document.positionAt(instructionNameEndOffset + start + argStart), this.document.positionAt(instructionNameEndOffset + start + mark))));
260 argStart = -1;
261 }
262 break argumentLoop;
263 }
264 else if (argStart === -1) {
265 argStart = i;
266 }
267 break;
268 default:
269 if (spaced) {
270 this.createSpacedArgument(argStart, args, content, mark, instructionNameEndOffset, start);
271 // reset to start a new argument
272 argStart = i;
273 spaced = false;
274 }
275 escaped = false;
276 if (argStart === -1) {
277 argStart = i;
278 }
279 // variable detected
280 if (char === '$' && content.charAt(i + 1) === '{') {
281 let singleQuotes = false;
282 let doubleQuotes = false;
283 let escaped = false;
284 for (let j = i + 1; j < content.length; j++) {
285 switch (content.charAt(j)) {
286 case this.escapeChar:
287 escaped = true;
288 break;
289 case '\r':
290 case '\n':
291 break;
292 case '\'':
293 singleQuotes = !singleQuotes;
294 escaped = false;
295 break;
296 case '"':
297 doubleQuotes = !doubleQuotes;
298 escaped = false;
299 break;
300 case ' ':
301 case '\t':
302 if (escaped || singleQuotes || doubleQuotes) {
303 break;
304 }
305 i = j - 1;
306 continue argumentLoop;
307 case '}':
308 i = j;
309 continue argumentLoop;
310 default:
311 escaped = false;
312 break;
313 }
314 }
315 break argumentLoop;
316 }
317 break;
318 }
319 }
320 if (argStart !== -1 && argStart !== content.length) {
321 let end = endingEscape ? content.length - 1 : content.length;
322 let value = content.substring(argStart, end);
323 args.push(new argument_1.Argument(value, vscode_languageserver_types_1.Range.create(this.document.positionAt(instructionNameEndOffset + start + argStart), this.document.positionAt(instructionNameEndOffset + start + end))));
324 }
325 return args;
326 }
327 createSpacedArgument(argStart, args, content, mark, instructionNameEndOffset, start) {
328 if (argStart !== -1) {
329 args.push(new argument_1.Argument(content.substring(argStart, mark), vscode_languageserver_types_1.Range.create(this.document.positionAt(instructionNameEndOffset + start + argStart), this.document.positionAt(instructionNameEndOffset + start + mark))));
330 }
331 }
332}
333exports.PropertyInstruction = PropertyInstruction;