UNPKG

29.3 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.Instruction = void 0;
4const vscode_languageserver_types_1 = require("vscode-languageserver-types");
5const util_1 = require("./util");
6const line_1 = require("./line");
7const argument_1 = require("./argument");
8const variable_1 = require("./variable");
9const main_1 = require("./main");
10class Instruction extends line_1.Line {
11 constructor(document, range, dockerfile, escapeChar, instruction, instructionRange) {
12 super(document, range);
13 this.dockerfile = dockerfile;
14 this.escapeChar = escapeChar;
15 this.instruction = instruction;
16 this.instructionRange = instructionRange;
17 }
18 toString() {
19 let value = this.getKeyword();
20 for (let arg of this.getRawArguments()) {
21 value += ' ';
22 value += arg.getValue();
23 }
24 return value;
25 }
26 getRangeContent(range) {
27 if (range === null) {
28 return null;
29 }
30 return this.document.getText().substring(this.document.offsetAt(range.start), this.document.offsetAt(range.end));
31 }
32 getInstructionRange() {
33 return this.instructionRange;
34 }
35 getInstruction() {
36 return this.instruction;
37 }
38 getKeyword() {
39 return this.getInstruction().toUpperCase();
40 }
41 getArgumentsRange() {
42 let args = this.getArguments();
43 if (args.length === 0) {
44 return null;
45 }
46 return vscode_languageserver_types_1.Range.create(args[0].getRange().start, args[args.length - 1].getRange().end);
47 }
48 getArgumentsRanges() {
49 let args = this.getArguments();
50 if (args.length === 0) {
51 return [];
52 }
53 if (args[0].getRange().start.line === args[args.length - 1].getRange().end.line) {
54 return [vscode_languageserver_types_1.Range.create(args[0].getRange().start, args[args.length - 1].getRange().end)];
55 }
56 let ranges = [];
57 let end = -1;
58 let startPosition = args[0].getRange().start;
59 let range = this.getInstructionRange();
60 let extra = this.document.offsetAt(startPosition) - this.document.offsetAt(range.start);
61 let content = this.getTextContent();
62 let fullArgs = content.substring(extra, this.document.offsetAt(args[args.length - 1].getRange().end) - this.document.offsetAt(range.start));
63 let offset = this.document.offsetAt(range.start) + extra;
64 let start = false;
65 let comment = false;
66 for (let i = 0; i < fullArgs.length; i++) {
67 let char = fullArgs.charAt(i);
68 if (char === this.escapeChar) {
69 let next = fullArgs.charAt(i + 1);
70 if (next === ' ' || next === '\t') {
71 whitespaceCheck: for (let j = i + 2; j < fullArgs.length; j++) {
72 switch (fullArgs.charAt(j)) {
73 case ' ':
74 case '\t':
75 continue;
76 case '\r':
77 j++;
78 case '\n':
79 if (startPosition !== null) {
80 ranges.push(vscode_languageserver_types_1.Range.create(startPosition, this.document.positionAt(offset + end + 1)));
81 }
82 startPosition = null;
83 start = true;
84 comment = false;
85 i = j;
86 break whitespaceCheck;
87 default:
88 break whitespaceCheck;
89 }
90 }
91 }
92 else if (next === '\r') {
93 if (startPosition !== null) {
94 ranges.push(vscode_languageserver_types_1.Range.create(startPosition, this.document.positionAt(offset + end + 1)));
95 startPosition = null;
96 }
97 start = true;
98 comment = false;
99 i += 2;
100 }
101 else if (next === '\n') {
102 if (startPosition !== null) {
103 ranges.push(vscode_languageserver_types_1.Range.create(startPosition, this.document.positionAt(offset + end + 1)));
104 }
105 startPosition = null;
106 start = true;
107 comment = false;
108 i++;
109 }
110 else {
111 i++;
112 }
113 }
114 else if (util_1.Util.isNewline(char)) {
115 if (comment) {
116 startPosition = null;
117 start = true;
118 comment = false;
119 }
120 }
121 else {
122 if (!comment) {
123 if (startPosition === null) {
124 if (char === '#') {
125 comment = true;
126 continue;
127 }
128 let position = this.document.positionAt(offset + i);
129 if (position.character !== 0) {
130 startPosition = vscode_languageserver_types_1.Position.create(position.line, 0);
131 }
132 }
133 end = i;
134 }
135 }
136 }
137 if (startPosition === null) {
138 // should only happen if the last argument is on its own line with
139 // no leading whitespace
140 ranges.push(vscode_languageserver_types_1.Range.create(this.document.positionAt(offset + end), this.document.positionAt(offset + end + 1)));
141 }
142 else {
143 ranges.push(vscode_languageserver_types_1.Range.create(startPosition, this.document.positionAt(offset + end + 1)));
144 }
145 return ranges;
146 }
147 getRawArgumentsContent() {
148 let args = this.getArguments();
149 if (args.length === 0) {
150 return null;
151 }
152 return this.getRangeContent(vscode_languageserver_types_1.Range.create(args[0].getRange().start, args[args.length - 1].getRange().end));
153 }
154 getArgumentsContent() {
155 let args = this.getArguments();
156 if (args.length === 0) {
157 return null;
158 }
159 let content = "";
160 let ranges = this.getArgumentsRanges();
161 let documentText = this.document.getText();
162 for (let range of ranges) {
163 content += documentText.substring(this.document.offsetAt(range.start), this.document.offsetAt(range.end));
164 }
165 return content;
166 }
167 getArguments() {
168 return this.getRawArguments();
169 }
170 getRawArguments() {
171 let args = [];
172 let range = this.getInstructionRange();
173 let extra = this.document.offsetAt(range.end) - this.document.offsetAt(range.start);
174 let content = this.getTextContent();
175 let fullArgs = content.substring(extra);
176 let offset = this.document.offsetAt(range.start) + extra;
177 let start = false;
178 let comment = false;
179 let found = -1;
180 // determines whether the parser has found a space or tab
181 // whitespace character that's a part of an escaped newline sequence
182 let escapedWhitespaceDetected = false;
183 // determines if the parser is currently in an escaped newline sequence
184 let escaping = false;
185 let escapeMarker = -1;
186 let escapedArg = "";
187 for (let i = 0; i < fullArgs.length; i++) {
188 let char = fullArgs.charAt(i);
189 if (util_1.Util.isWhitespace(char)) {
190 if (escaping) {
191 escapedWhitespaceDetected = true;
192 if (util_1.Util.isNewline(char)) {
193 // reached a newline, any previously
194 // detected whitespace should be ignored
195 escapedWhitespaceDetected = false;
196 if (comment) {
197 // reached a newline, no longer in a comment
198 comment = false;
199 start = true;
200 }
201 }
202 continue;
203 }
204 else if (found !== -1) {
205 if (escapeMarker === -1) {
206 args.push(new argument_1.Argument(escapedArg, vscode_languageserver_types_1.Range.create(this.document.positionAt(offset + found), this.document.positionAt(offset + i))));
207 }
208 else {
209 args.push(new argument_1.Argument(escapedArg, vscode_languageserver_types_1.Range.create(this.document.positionAt(offset + found), this.document.positionAt(offset + escapeMarker))));
210 }
211 escapeMarker = -1;
212 escapedArg = "";
213 found = -1;
214 }
215 }
216 else if (char === this.escapeChar) {
217 let next = fullArgs.charAt(i + 1);
218 if (next === ' ' || next === '\t') {
219 whitespaceCheck: for (let j = i + 2; j < fullArgs.length; j++) {
220 let newlineCheck = fullArgs.charAt(j);
221 switch (newlineCheck) {
222 case ' ':
223 case '\t':
224 continue;
225 case '\r':
226 j++;
227 case '\n':
228 comment = false;
229 escaping = true;
230 start = true;
231 if (found !== -1) {
232 escapeMarker = i;
233 }
234 i = j;
235 break whitespaceCheck;
236 default:
237 escapeMarker = i;
238 if (found === -1) {
239 i = j - 1;
240 }
241 break whitespaceCheck;
242 }
243 }
244 }
245 else if (next === '\r') {
246 comment = false;
247 escaping = true;
248 start = true;
249 if (found !== -1 && escapeMarker === -1) {
250 escapeMarker = i;
251 }
252 i += 2;
253 }
254 else if (next === '\n') {
255 comment = false;
256 escaping = true;
257 start = true;
258 if (found !== -1 && escapeMarker === -1) {
259 escapeMarker = i;
260 }
261 i++;
262 }
263 else {
264 if (escapedWhitespaceDetected && escapeMarker !== -1) {
265 args.push(new argument_1.Argument(escapedArg, vscode_languageserver_types_1.Range.create(this.document.positionAt(offset + found), this.document.positionAt(offset + escapeMarker))));
266 escapedArg = "";
267 found = -1;
268 }
269 escapeMarker = -1;
270 escapedWhitespaceDetected = false;
271 escaping = false;
272 if (next === '$') {
273 escapedArg = escapedArg + char + next;
274 }
275 else if (next === '') {
276 // reached EOF, stop processing
277 break;
278 }
279 else {
280 escapedArg = escapedArg + next;
281 }
282 if (found === -1) {
283 found = i;
284 }
285 i++;
286 }
287 }
288 else if (!comment) {
289 if (start && char === '#') {
290 comment = true;
291 }
292 else {
293 if (escapedWhitespaceDetected && escapeMarker !== -1) {
294 args.push(new argument_1.Argument(escapedArg, vscode_languageserver_types_1.Range.create(this.document.positionAt(offset + found), this.document.positionAt(offset + escapeMarker))));
295 escapedArg = "";
296 found = -1;
297 }
298 escapedWhitespaceDetected = false;
299 escaping = false;
300 escapeMarker = -1;
301 escapedArg = escapedArg + char;
302 if (found === -1) {
303 found = i;
304 }
305 }
306 // non-whitespace character detected, reset
307 start = false;
308 }
309 }
310 if (found !== -1) {
311 if (escapeMarker === -1) {
312 args.push(new argument_1.Argument(escapedArg, vscode_languageserver_types_1.Range.create(this.document.positionAt(offset + found), this.document.positionAt(offset + fullArgs.length))));
313 }
314 else {
315 args.push(new argument_1.Argument(escapedArg, vscode_languageserver_types_1.Range.create(this.document.positionAt(offset + found), this.document.positionAt(offset + escapeMarker))));
316 }
317 }
318 return args;
319 }
320 getExpandedArguments() {
321 let args = this.getArguments();
322 for (let i = 0; i < args.length; i++) {
323 const argRange = args[i].getRange();
324 let offset = this.document.offsetAt(argRange.start);
325 const variables = this.parseVariables(offset, args[i].getValue());
326 const swaps = [];
327 let requiresExpansion = false;
328 for (let variable of variables) {
329 const value = this.dockerfile.resolveVariable(variable.getName(), variable.getNameRange().start.line);
330 swaps.push(value);
331 requiresExpansion = requiresExpansion || value !== undefined;
332 }
333 if (requiresExpansion) {
334 let expanded = "";
335 for (let j = 0; j < swaps.length; j++) {
336 const variableRange = variables[j].getRange();
337 const start = this.document.offsetAt(variableRange.start);
338 const end = this.document.offsetAt(variableRange.end);
339 if (swaps[j]) {
340 // replace variable with its resolved value
341 expanded += this.document.getText().substring(offset, start);
342 expanded += swaps[j];
343 offset = end;
344 }
345 else {
346 expanded += this.document.getText().substring(offset, end);
347 offset = end;
348 }
349 }
350 const argEnd = this.document.offsetAt(argRange.end);
351 if (argEnd !== offset) {
352 // if the variable's range doesn't match the argument,
353 // append the remaining text
354 expanded += this.document.getText().substring(offset, argEnd);
355 }
356 args[i] = new argument_1.Argument(expanded, argRange);
357 }
358 }
359 return args;
360 }
361 getVariables() {
362 const variables = [];
363 const args = this.getRawArguments();
364 for (const arg of args) {
365 let range = arg.getRange();
366 let rawValue = this.document.getText().substring(this.document.offsetAt(range.start), this.document.offsetAt(range.end));
367 const parsedVariables = this.parseVariables(this.document.offsetAt(arg.getRange().start), rawValue);
368 for (const parsedVariable of parsedVariables) {
369 variables.push(parsedVariable);
370 }
371 }
372 return variables;
373 }
374 parseVariables(offset, arg) {
375 let variables = [];
376 variableLoop: for (let i = 0; i < arg.length; i++) {
377 switch (arg.charAt(i)) {
378 case this.escapeChar:
379 if (arg.charAt(i + 1) === '$') {
380 i++;
381 }
382 break;
383 case '$':
384 if (arg.charAt(i + 1) === '{') {
385 let escapedString = "${";
386 let escapedName = "";
387 let nameEnd = -1;
388 let escapedSubstitutionParameter = "";
389 let substitutionStart = -1;
390 let substitutionEnd = -1;
391 let modifierRead = -1;
392 nameLoop: for (let j = i + 2; j < arg.length; j++) {
393 let char = arg.charAt(j);
394 switch (char) {
395 case this.escapeChar:
396 for (let k = j + 1; k < arg.length; k++) {
397 switch (arg.charAt(k)) {
398 case ' ':
399 case '\t':
400 case '\r':
401 // ignore whitespace
402 continue;
403 case '\n':
404 // escape this newline
405 j = k;
406 continue nameLoop;
407 }
408 }
409 break;
410 case '}':
411 escapedString += '}';
412 let modifier = null;
413 let modifierRange = null;
414 let substitutionParameter = modifierRead !== -1 ? escapedSubstitutionParameter : null;
415 let substitutionRange = null;
416 if (nameEnd === -1) {
417 nameEnd = j;
418 }
419 else if (nameEnd + 1 === j) {
420 modifier = "";
421 modifierRange = vscode_languageserver_types_1.Range.create(this.document.positionAt(offset + nameEnd + 1), this.document.positionAt(offset + nameEnd + 1));
422 }
423 else {
424 if (substitutionStart === -1) {
425 // no substitution parameter found,
426 // but a modifier character existed,
427 // just offset the range by 1 from
428 // the modifier character
429 substitutionStart = modifierRead + 1;
430 substitutionEnd = modifierRead + 1;
431 }
432 else {
433 // offset one more from the last
434 // character found
435 substitutionEnd = substitutionEnd + 1;
436 }
437 modifier = arg.substring(modifierRead, modifierRead + 1);
438 modifierRange = vscode_languageserver_types_1.Range.create(this.document.positionAt(offset + modifierRead), this.document.positionAt(offset + modifierRead + 1));
439 substitutionRange = vscode_languageserver_types_1.Range.create(this.document.positionAt(offset + substitutionStart), this.document.positionAt(offset + substitutionEnd));
440 }
441 let start = this.document.positionAt(offset + i);
442 variables.push(new variable_1.Variable(escapedName, vscode_languageserver_types_1.Range.create(this.document.positionAt(offset + i + 2), this.document.positionAt(offset + nameEnd)), vscode_languageserver_types_1.Range.create(start, this.document.positionAt(offset + j + 1)), modifier, modifierRange, substitutionParameter, substitutionRange, this.dockerfile.resolveVariable(escapedName, start.line) !== undefined, this.isBuildVariable(escapedName, start.line), escapedString));
443 i = j;
444 continue variableLoop;
445 case ':':
446 if (nameEnd === -1) {
447 nameEnd = j;
448 }
449 else if (modifierRead !== -1) {
450 if (substitutionStart === -1) {
451 substitutionStart = j;
452 substitutionEnd = j;
453 }
454 else {
455 substitutionEnd = j;
456 }
457 escapedSubstitutionParameter += ':';
458 }
459 else {
460 modifierRead = j;
461 }
462 escapedString += ':';
463 break;
464 case '\n':
465 case '\r':
466 case ' ':
467 case '\t':
468 break;
469 default:
470 if (nameEnd === -1) {
471 escapedName += char;
472 }
473 else if (modifierRead !== -1) {
474 if (substitutionStart === -1) {
475 substitutionStart = j;
476 substitutionEnd = j;
477 }
478 else {
479 substitutionEnd = j;
480 }
481 escapedSubstitutionParameter += char;
482 }
483 else {
484 modifierRead = j;
485 }
486 escapedString += char;
487 break;
488 }
489 }
490 // no } found, not a valid variable, stop processing
491 break variableLoop;
492 }
493 else if (util_1.Util.isWhitespace(arg.charAt(i + 1)) || i === arg.length - 1) {
494 // $ followed by whitespace or EOF, ignore this variable
495 continue;
496 }
497 else {
498 let escapedName = "";
499 nameLoop: for (let j = i + 1; j < arg.length; j++) {
500 let char = arg.charAt(j);
501 switch (char) {
502 case '\r':
503 case '\n':
504 case ' ':
505 case '\t':
506 continue;
507 case '$':
508 case '\'':
509 case '"':
510 let varStart = this.document.positionAt(offset + i);
511 variables.push(new variable_1.Variable(escapedName, vscode_languageserver_types_1.Range.create(this.document.positionAt(offset + i + 1), this.document.positionAt(offset + j)), vscode_languageserver_types_1.Range.create(varStart, this.document.positionAt(offset + j)), null, null, null, null, this.dockerfile.resolveVariable(escapedName, varStart.line) !== undefined, this.isBuildVariable(escapedName, varStart.line), '$' + escapedName));
512 i = j - 1;
513 continue variableLoop;
514 case this.escapeChar:
515 for (let k = j + 1; k < arg.length; k++) {
516 switch (arg.charAt(k)) {
517 case ' ':
518 case '\t':
519 case '\r':
520 // ignore whitespace
521 continue;
522 case '\n':
523 // escape this newline
524 j = k;
525 continue nameLoop;
526 }
527 }
528 // reached EOF after an escape character
529 let start = this.document.positionAt(offset + i);
530 variables.push(new variable_1.Variable(escapedName, vscode_languageserver_types_1.Range.create(this.document.positionAt(offset + i + 1), this.document.positionAt(offset + j)), vscode_languageserver_types_1.Range.create(start, this.document.positionAt(offset + j)), null, null, null, null, this.dockerfile.resolveVariable(escapedName, start.line) !== undefined, this.isBuildVariable(escapedName, start.line), '$' + escapedName));
531 break variableLoop;
532 }
533 if (char.match(/^[a-z0-9_]+$/i) === null) {
534 let varStart = this.document.positionAt(offset + i);
535 variables.push(new variable_1.Variable(escapedName, vscode_languageserver_types_1.Range.create(this.document.positionAt(offset + i + 1), this.document.positionAt(offset + j)), vscode_languageserver_types_1.Range.create(varStart, this.document.positionAt(offset + j)), null, null, null, null, this.dockerfile.resolveVariable(escapedName, varStart.line) !== undefined, this.isBuildVariable(escapedName, varStart.line), '$' + escapedName));
536 i = j - 1;
537 continue variableLoop;
538 }
539 escapedName += char;
540 }
541 let start = this.document.positionAt(offset + i);
542 variables.push(new variable_1.Variable(escapedName, vscode_languageserver_types_1.Range.create(this.document.positionAt(offset + i + 1), this.document.positionAt(offset + arg.length)), vscode_languageserver_types_1.Range.create(start, this.document.positionAt(offset + arg.length)), null, null, null, null, this.dockerfile.resolveVariable(escapedName, start.line) !== undefined, this.isBuildVariable(escapedName, start.line), '$' + escapedName));
543 }
544 break variableLoop;
545 }
546 }
547 return variables;
548 }
549 isBuildVariable(variable, line) {
550 if (this.getKeyword() === main_1.Keyword.FROM) {
551 for (const initialArg of this.dockerfile.getInitialARGs()) {
552 const arg = initialArg;
553 const property = arg.getProperty();
554 if (property && variable === property.getName()) {
555 return true;
556 }
557 }
558 return undefined;
559 }
560 let image = this.dockerfile.getContainingImage(vscode_languageserver_types_1.Position.create(line, 0));
561 let envs = image.getENVs();
562 for (let i = envs.length - 1; i >= 0; i--) {
563 if (envs[i].isBefore(line)) {
564 for (let property of envs[i].getProperties()) {
565 if (property.getName() === variable) {
566 return false;
567 }
568 }
569 }
570 }
571 let args = image.getARGs();
572 for (let i = args.length - 1; i >= 0; i--) {
573 if (args[i].isBefore(line)) {
574 let property = args[i].getProperty();
575 if (property && property.getName() === variable) {
576 return true;
577 }
578 }
579 }
580 return undefined;
581 }
582}
583exports.Instruction = Instruction;