UNPKG

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