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