UNPKG

20.7 kBJavaScriptView Raw
1/* --------------------------------------------------------------------------------------------
2 * Copyright (c) Remy Suen. All rights reserved.
3 * Licensed under the MIT License. See License.txt in the project root for license information.
4 * ------------------------------------------------------------------------------------------ */
5'use strict';
6Object.defineProperty(exports, "__esModule", { value: true });
7const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
8const vscode_languageserver_types_1 = require("vscode-languageserver-types");
9const comment_1 = require("./comment");
10const parserDirective_1 = require("./parserDirective");
11const instruction_1 = require("./instruction");
12const add_1 = require("./instructions/add");
13const arg_1 = require("./instructions/arg");
14const cmd_1 = require("./instructions/cmd");
15const copy_1 = require("./instructions/copy");
16const env_1 = require("./instructions/env");
17const entrypoint_1 = require("./instructions/entrypoint");
18const from_1 = require("./instructions/from");
19const healthcheck_1 = require("./instructions/healthcheck");
20const label_1 = require("./instructions/label");
21const onbuild_1 = require("./instructions/onbuild");
22const run_1 = require("./instructions/run");
23const shell_1 = require("./instructions/shell");
24const stopsignal_1 = require("./instructions/stopsignal");
25const workdir_1 = require("./instructions/workdir");
26const user_1 = require("./instructions/user");
27const volume_1 = require("./instructions/volume");
28const dockerfile_1 = require("./dockerfile");
29const main_1 = require("./main");
30class Parser {
31 constructor() {
32 this.escapeChar = null;
33 }
34 static createInstruction(document, dockerfile, escapeChar, lineRange, instruction, instructionRange) {
35 switch (instruction.toUpperCase()) {
36 case "ADD":
37 return new add_1.Add(document, lineRange, dockerfile, escapeChar, instruction, instructionRange);
38 case "ARG":
39 return new arg_1.Arg(document, lineRange, dockerfile, escapeChar, instruction, instructionRange);
40 case "CMD":
41 return new cmd_1.Cmd(document, lineRange, dockerfile, escapeChar, instruction, instructionRange);
42 case "COPY":
43 return new copy_1.Copy(document, lineRange, dockerfile, escapeChar, instruction, instructionRange);
44 case "ENTRYPOINT":
45 return new entrypoint_1.Entrypoint(document, lineRange, dockerfile, escapeChar, instruction, instructionRange);
46 case "ENV":
47 return new env_1.Env(document, lineRange, dockerfile, escapeChar, instruction, instructionRange);
48 case "FROM":
49 return new from_1.From(document, lineRange, dockerfile, escapeChar, instruction, instructionRange);
50 case "HEALTHCHECK":
51 return new healthcheck_1.Healthcheck(document, lineRange, dockerfile, escapeChar, instruction, instructionRange);
52 case "LABEL":
53 return new label_1.Label(document, lineRange, dockerfile, escapeChar, instruction, instructionRange);
54 case "ONBUILD":
55 return new onbuild_1.Onbuild(document, lineRange, dockerfile, escapeChar, instruction, instructionRange);
56 case "RUN":
57 return new run_1.Run(document, lineRange, dockerfile, escapeChar, instruction, instructionRange);
58 case "SHELL":
59 return new shell_1.Shell(document, lineRange, dockerfile, escapeChar, instruction, instructionRange);
60 case "STOPSIGNAL":
61 return new stopsignal_1.Stopsignal(document, lineRange, dockerfile, escapeChar, instruction, instructionRange);
62 case "WORKDIR":
63 return new workdir_1.Workdir(document, lineRange, dockerfile, escapeChar, instruction, instructionRange);
64 case "USER":
65 return new user_1.User(document, lineRange, dockerfile, escapeChar, instruction, instructionRange);
66 case "VOLUME":
67 return new volume_1.Volume(document, lineRange, dockerfile, escapeChar, instruction, instructionRange);
68 }
69 return new instruction_1.Instruction(document, lineRange, dockerfile, escapeChar, instruction, instructionRange);
70 }
71 getParserDirectives(document, buffer) {
72 // reset the escape directive in between runs
73 const directives = [];
74 this.escapeChar = '';
75 directiveCheck: for (let i = 0; i < buffer.length; i++) {
76 switch (buffer.charAt(i)) {
77 case ' ':
78 case '\t':
79 break;
80 case '\r':
81 case '\n':
82 // blank lines stop the parsing of directives immediately
83 break directiveCheck;
84 case '#':
85 let directiveStart = -1;
86 let directiveEnd = -1;
87 for (let j = i + 1; j < buffer.length; j++) {
88 let char = buffer.charAt(j);
89 switch (char) {
90 case ' ':
91 case '\t':
92 if (directiveStart !== -1 && directiveEnd === -1) {
93 directiveEnd = j;
94 }
95 break;
96 case '\r':
97 case '\n':
98 break directiveCheck;
99 case '=':
100 let valueStart = -1;
101 let valueEnd = -1;
102 if (directiveEnd === -1) {
103 directiveEnd = j;
104 }
105 // assume the line ends with the file
106 let lineEnd = buffer.length;
107 directiveValue: for (let k = j + 1; k < buffer.length; k++) {
108 char = buffer.charAt(k);
109 switch (char) {
110 case '\r':
111 case '\n':
112 if (valueStart !== -1 && valueEnd === -1) {
113 valueEnd = k;
114 }
115 // line break found, reset
116 lineEnd = k;
117 break directiveValue;
118 case '\t':
119 case ' ':
120 if (valueStart !== -1 && valueEnd === -1) {
121 valueEnd = k;
122 }
123 continue;
124 default:
125 if (valueStart === -1) {
126 valueStart = k;
127 }
128 break;
129 }
130 }
131 if (directiveStart === -1) {
132 // no directive, it's a regular comment
133 break directiveCheck;
134 }
135 if (valueStart === -1) {
136 // no non-whitespace characters found, highlight all the characters then
137 valueStart = j + 1;
138 valueEnd = lineEnd;
139 }
140 else if (valueEnd === -1) {
141 // reached EOF
142 valueEnd = buffer.length;
143 }
144 const lineRange = vscode_languageserver_types_1.Range.create(document.positionAt(i), document.positionAt(lineEnd));
145 const nameRange = vscode_languageserver_types_1.Range.create(document.positionAt(directiveStart), document.positionAt(directiveEnd));
146 const valueRange = vscode_languageserver_types_1.Range.create(document.positionAt(valueStart), document.positionAt(valueEnd));
147 directives.push(new parserDirective_1.ParserDirective(document, lineRange, nameRange, valueRange));
148 directiveStart = -1;
149 if (buffer.charAt(valueEnd) === '\r') {
150 // skip over the \r
151 i = valueEnd + 1;
152 }
153 else {
154 i = valueEnd;
155 }
156 continue directiveCheck;
157 default:
158 if (directiveStart === -1) {
159 directiveStart = j;
160 }
161 break;
162 }
163 }
164 break;
165 default:
166 break directiveCheck;
167 }
168 }
169 return directives;
170 }
171 parse(buffer) {
172 this.document = vscode_languageserver_textdocument_1.TextDocument.create("", "", 0, buffer);
173 this.buffer = buffer;
174 let dockerfile = new dockerfile_1.Dockerfile(this.document);
175 let directives = this.getParserDirectives(this.document, this.buffer);
176 let offset = 0;
177 this.escapeChar = '\\';
178 if (directives.length > 0) {
179 dockerfile.setDirectives(directives);
180 this.escapeChar = dockerfile.getEscapeCharacter();
181 // start parsing after the directives
182 offset = this.document.offsetAt(vscode_languageserver_types_1.Position.create(directives.length, 0));
183 }
184 for (let i = offset; i < this.buffer.length; i++) {
185 const char = this.buffer.charAt(i);
186 switch (char) {
187 case ' ':
188 case '\t':
189 case '\r':
190 case '\n':
191 break;
192 case '#':
193 i = this.processComment(dockerfile, i);
194 break;
195 default:
196 i = this.processInstruction(dockerfile, char, i);
197 break;
198 }
199 }
200 dockerfile.organizeComments();
201 return dockerfile;
202 }
203 processInstruction(dockerfile, char, start) {
204 let instruction = char;
205 let instructionEnd = -1;
206 let escapedInstruction = false;
207 instructionCheck: for (let i = start + 1; i < this.buffer.length; i++) {
208 char = this.buffer.charAt(i);
209 switch (char) {
210 case this.escapeChar:
211 escapedInstruction = true;
212 char = this.buffer.charAt(i + 1);
213 if (char === '\r' || char === '\n') {
214 if (instructionEnd === -1) {
215 instructionEnd = i;
216 }
217 i++;
218 }
219 else if (char === ' ' || char === '\t') {
220 for (let j = i + 2; j < this.buffer.length; j++) {
221 switch (this.buffer.charAt(j)) {
222 case ' ':
223 case '\t':
224 break;
225 case '\r':
226 case '\n':
227 i = j;
228 continue instructionCheck;
229 default:
230 // found an argument, mark end of instruction
231 instructionEnd = i + 1;
232 instruction = instruction + this.escapeChar;
233 i = j - 2;
234 continue instructionCheck;
235 }
236 }
237 // reached EOF
238 instructionEnd = i + 1;
239 instruction = instruction + this.escapeChar;
240 break instructionCheck;
241 }
242 else {
243 instructionEnd = i + 1;
244 instruction = instruction + this.escapeChar;
245 // reset and consider it as one contiguous word
246 escapedInstruction = false;
247 }
248 break;
249 case ' ':
250 case '\t':
251 if (escapedInstruction) {
252 // on an escaped newline, need to search for non-whitespace
253 escapeCheck: for (let j = i + 1; j < this.buffer.length; j++) {
254 switch (this.buffer.charAt(j)) {
255 case ' ':
256 case '\t':
257 break;
258 case '\r':
259 case '\n':
260 i = j;
261 continue instructionCheck;
262 default:
263 break escapeCheck;
264 }
265 }
266 escapedInstruction = false;
267 }
268 if (instructionEnd === -1) {
269 instructionEnd = i;
270 }
271 i = this.processArguments(dockerfile, instruction, instructionEnd, start, i);
272 dockerfile.addInstruction(this.createInstruction(dockerfile, instruction, start, instructionEnd, i));
273 return i;
274 case '\r':
275 case '\n':
276 if (escapedInstruction) {
277 continue;
278 }
279 if (instructionEnd === -1) {
280 instructionEnd = i;
281 }
282 dockerfile.addInstruction(this.createInstruction(dockerfile, instruction, start, i, i));
283 return i;
284 case '#':
285 if (escapedInstruction) {
286 continue;
287 }
288 default:
289 instructionEnd = i + 1;
290 instruction = instruction + char;
291 escapedInstruction = false;
292 break;
293 }
294 }
295 // reached EOF
296 if (instructionEnd === -1) {
297 instructionEnd = this.buffer.length;
298 }
299 dockerfile.addInstruction(this.createInstruction(dockerfile, instruction, start, instructionEnd, this.buffer.length));
300 return this.buffer.length;
301 }
302 parseHeredocName(value) {
303 value = value.substring(2);
304 if (value.charAt(0) === '-') {
305 value = value.substring(1);
306 }
307 if (value.charAt(0) === '"' || value.charAt(0) === '\'') {
308 return value.substring(1, value.length - 1);
309 }
310 return value;
311 }
312 processHeredocs(instruction, offset) {
313 let keyword = instruction.getKeyword();
314 if (keyword === main_1.Keyword.ONBUILD) {
315 instruction = instruction.getTriggerInstruction();
316 if (instruction === null) {
317 return offset;
318 }
319 keyword = instruction.getKeyword();
320 }
321 if (keyword !== main_1.Keyword.ADD && keyword !== main_1.Keyword.COPY && keyword !== main_1.Keyword.RUN) {
322 return offset;
323 }
324 const heredocs = [];
325 for (const arg of instruction.getArguments()) {
326 const value = arg.getValue();
327 if (value.startsWith("<<") && value.length > 2) {
328 heredocs.push(this.parseHeredocName(value));
329 }
330 }
331 if (heredocs.length > 0) {
332 for (const heredoc of heredocs) {
333 offset = this.parseHeredoc(heredoc, offset);
334 }
335 }
336 return offset;
337 }
338 processArguments(dockerfile, instruction, instructionEnd, start, offset) {
339 let escaped = false;
340 argumentsCheck: for (let i = offset + 1; i < this.buffer.length; i++) {
341 switch (this.buffer.charAt(i)) {
342 case '\r':
343 case '\n':
344 if (escaped) {
345 continue;
346 }
347 return this.processHeredocs(this.createInstruction(dockerfile, instruction, start, instructionEnd, i), i);
348 case this.escapeChar:
349 const next = this.buffer.charAt(i + 1);
350 if (next === '\n' || next === '\r') {
351 escaped = true;
352 i++;
353 }
354 else if (next === ' ' || next === '\t') {
355 for (let j = i + 2; j < this.buffer.length; j++) {
356 switch (this.buffer.charAt(j)) {
357 case ' ':
358 case '\t':
359 break;
360 case '\r':
361 case '\n':
362 escaped = true;
363 default:
364 i = j;
365 continue argumentsCheck;
366 }
367 }
368 // reached EOF
369 return this.buffer.length;
370 }
371 continue;
372 case '#':
373 if (escaped) {
374 i = this.processComment(dockerfile, i);
375 continue argumentsCheck;
376 }
377 break;
378 case ' ':
379 case '\t':
380 break;
381 default:
382 if (escaped) {
383 escaped = false;
384 }
385 break;
386 }
387 }
388 return this.buffer.length;
389 }
390 processComment(dockerfile, start) {
391 let end = this.buffer.length;
392 commentLoop: for (let i = start + 1; i < this.buffer.length; i++) {
393 switch (this.buffer.charAt(i)) {
394 case '\r':
395 case '\n':
396 end = i;
397 break commentLoop;
398 }
399 }
400 const range = vscode_languageserver_types_1.Range.create(this.document.positionAt(start), this.document.positionAt(end));
401 dockerfile.addComment(new comment_1.Comment(this.document, range));
402 return end;
403 }
404 parseHeredoc(heredocName, offset) {
405 let startWord = -1;
406 let lineStart = true;
407 for (let i = offset; i < this.buffer.length; i++) {
408 switch (this.buffer.charAt(i)) {
409 case ' ':
410 case '\t':
411 lineStart = false;
412 break;
413 case '\r':
414 case '\n':
415 if (startWord !== -1 && heredocName === this.buffer.substring(startWord, i)) {
416 return i;
417 }
418 startWord = -1;
419 lineStart = true;
420 break;
421 default:
422 if (lineStart) {
423 startWord = i;
424 lineStart = false;
425 }
426 break;
427 }
428 }
429 return this.buffer.length;
430 }
431 createInstruction(dockerfile, instruction, start, instructionEnd, end) {
432 const startPosition = this.document.positionAt(start);
433 const instructionRange = vscode_languageserver_types_1.Range.create(startPosition, this.document.positionAt(instructionEnd));
434 const lineRange = vscode_languageserver_types_1.Range.create(startPosition, this.document.positionAt(end));
435 return Parser.createInstruction(this.document, dockerfile, this.escapeChar, lineRange, instruction, instructionRange);
436 }
437}
438exports.Parser = Parser;