1 |
|
2 |
|
3 |
|
4 |
|
5 | 'use strict';
|
6 | Object.defineProperty(exports, "__esModule", { value: true });
|
7 | exports.Parser = void 0;
|
8 | const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
|
9 | const vscode_languageserver_types_1 = require("vscode-languageserver-types");
|
10 | const comment_1 = require("./comment");
|
11 | const parserDirective_1 = require("./parserDirective");
|
12 | const instruction_1 = require("./instruction");
|
13 | const add_1 = require("./instructions/add");
|
14 | const arg_1 = require("./instructions/arg");
|
15 | const cmd_1 = require("./instructions/cmd");
|
16 | const copy_1 = require("./instructions/copy");
|
17 | const env_1 = require("./instructions/env");
|
18 | const entrypoint_1 = require("./instructions/entrypoint");
|
19 | const from_1 = require("./instructions/from");
|
20 | const healthcheck_1 = require("./instructions/healthcheck");
|
21 | const label_1 = require("./instructions/label");
|
22 | const onbuild_1 = require("./instructions/onbuild");
|
23 | const run_1 = require("./instructions/run");
|
24 | const shell_1 = require("./instructions/shell");
|
25 | const stopsignal_1 = require("./instructions/stopsignal");
|
26 | const workdir_1 = require("./instructions/workdir");
|
27 | const user_1 = require("./instructions/user");
|
28 | const volume_1 = require("./instructions/volume");
|
29 | const dockerfile_1 = require("./dockerfile");
|
30 | const main_1 = require("./main");
|
31 | class 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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
134 | break directiveCheck;
|
135 | }
|
136 | if (valueStart === -1) {
|
137 |
|
138 | valueStart = j + 1;
|
139 | valueEnd = lineEnd;
|
140 | }
|
141 | else if (valueEnd === -1) {
|
142 |
|
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 |
|
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 |
|
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 |
|
232 | instructionEnd = i + 1;
|
233 | instruction = instruction + this.escapeChar;
|
234 | i = j - 2;
|
235 | continue instructionCheck;
|
236 | }
|
237 | }
|
238 |
|
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 |
|
247 | escapedInstruction = false;
|
248 | }
|
249 | break;
|
250 | case ' ':
|
251 | case '\t':
|
252 | if (escapedInstruction) {
|
253 |
|
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 |
|
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 |
|
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 | }
|
439 | exports.Parser = Parser;
|