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