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