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 util_1 = require("./util");
|
9 | class Property {
|
10 | constructor(document, escapeChar, arg, arg2) {
|
11 | this.assignmentOperatorRange = null;
|
12 | this.assignmentOperator = null;
|
13 | this.valueRange = null;
|
14 | this.value = null;
|
15 | this.document = document;
|
16 | this.escapeChar = escapeChar;
|
17 | this.nameRange = Property.getNameRange(document, arg);
|
18 | let value = document.getText().substring(document.offsetAt(this.nameRange.start), document.offsetAt(this.nameRange.end));
|
19 | this.name = Property.getValue(value, escapeChar);
|
20 | if (arg2) {
|
21 | this.valueRange = arg2.getRange();
|
22 | value = document.getText().substring(document.offsetAt(this.valueRange.start), document.offsetAt(this.valueRange.end));
|
23 | this.value = Property.getValue(value, escapeChar);
|
24 | this.range = vscode_languageserver_types_1.Range.create(this.nameRange.start, this.valueRange.end);
|
25 | }
|
26 | else {
|
27 | let argRange = arg.getRange();
|
28 | if (this.nameRange.start.line === argRange.start.line
|
29 | && this.nameRange.start.character === argRange.start.character
|
30 | && this.nameRange.end.line === argRange.end.line
|
31 | && this.nameRange.end.character === argRange.end.character) {
|
32 | }
|
33 | else {
|
34 | this.valueRange = Property.getValueRange(document, arg);
|
35 | value = document.getText().substring(document.offsetAt(this.valueRange.start), document.offsetAt(this.valueRange.end));
|
36 | this.value = Property.getValue(value, escapeChar);
|
37 | this.assignmentOperatorRange = vscode_languageserver_types_1.Range.create(this.nameRange.end, this.valueRange.start);
|
38 | this.assignmentOperator = "=";
|
39 | }
|
40 | this.range = argRange;
|
41 | }
|
42 | }
|
43 | getRange() {
|
44 | return this.range;
|
45 | }
|
46 | getName() {
|
47 | return this.name;
|
48 | }
|
49 | getNameRange() {
|
50 | return this.nameRange;
|
51 | }
|
52 | getValue() {
|
53 | return this.value;
|
54 | }
|
55 | getValueRange() {
|
56 | return this.valueRange;
|
57 | }
|
58 | /**
|
59 | * Retrieves the operator used for delimiting between the name and
|
60 | * value of this property. This will either be the "=" character
|
61 | * or null if a character was not used or if this property has no
|
62 | * value defined.
|
63 | */
|
64 | getAssignmentOperator() {
|
65 | return this.assignmentOperator;
|
66 | }
|
67 | getAssignmentOperatorRange() {
|
68 | return this.assignmentOperatorRange;
|
69 | }
|
70 | /**
|
71 | * Returns the value of this property including any enclosing
|
72 | * single or double quotes and relevant escape characters.
|
73 | * Escaped newlines and its associated contiguous whitespace
|
74 | * characters however will not be returned as they are deemed to
|
75 | * be uninteresting to clients trying to return a Dockerfile.
|
76 | *
|
77 | * @return the unescaped value of this property or null if this
|
78 | * property has no associated value
|
79 | */
|
80 | getUnescapedValue() {
|
81 | if (this.valueRange === null) {
|
82 | return null;
|
83 | }
|
84 | let escaped = false;
|
85 | let rawValue = "";
|
86 | let value = this.document.getText().substring(this.document.offsetAt(this.valueRange.start), this.document.offsetAt(this.valueRange.end));
|
87 | rawLoop: for (let i = 0; i < value.length; i++) {
|
88 | let char = value.charAt(i);
|
89 | switch (char) {
|
90 | case this.escapeChar:
|
91 | for (let j = i + 1; j < value.length; j++) {
|
92 | switch (value.charAt(j)) {
|
93 | case '\r':
|
94 | j++;
|
95 | case '\n':
|
96 | escaped = true;
|
97 | i = j;
|
98 | continue rawLoop;
|
99 | case ' ':
|
100 | case '\t':
|
101 | break;
|
102 | default:
|
103 | rawValue = rawValue + char;
|
104 | continue rawLoop;
|
105 | }
|
106 | }
|
107 | // this happens if there's only whitespace after the escape character
|
108 | rawValue = rawValue + char;
|
109 | break;
|
110 | case '\r':
|
111 | case '\n':
|
112 | break;
|
113 | case ' ':
|
114 | case '\t':
|
115 | if (!escaped) {
|
116 | rawValue = rawValue + char;
|
117 | }
|
118 | break;
|
119 | case '#':
|
120 | if (escaped) {
|
121 | for (let j = i + 1; j < value.length; j++) {
|
122 | switch (value.charAt(j)) {
|
123 | case '\r':
|
124 | j++;
|
125 | case '\n':
|
126 | i = j;
|
127 | continue rawLoop;
|
128 | }
|
129 | }
|
130 | }
|
131 | else {
|
132 | rawValue = rawValue + char;
|
133 | }
|
134 | break;
|
135 | default:
|
136 | rawValue = rawValue + char;
|
137 | escaped = false;
|
138 | break;
|
139 | }
|
140 | }
|
141 | return rawValue;
|
142 | }
|
143 | static getNameRange(document, arg) {
|
144 | let value = arg.getValue();
|
145 | let index = value.indexOf('=');
|
146 | if (index !== -1) {
|
147 | let initial = value.charAt(0);
|
148 | let before = value.charAt(index - 1);
|
149 | // check if content before the equals sign are in quotes
|
150 | // "var"=value
|
151 | // 'var'=value
|
152 | // otherwise, just assume it's a standard definition
|
153 | // var=value
|
154 | if ((initial === '"' && before === '"') || (initial === '\'' && before === '\'') || (initial !== '"' && initial !== '\'')) {
|
155 | return vscode_languageserver_types_1.Range.create(arg.getRange().start, document.positionAt(document.offsetAt(arg.getRange().start) + index));
|
156 | }
|
157 | }
|
158 | // no '=' found, just defined the property's name
|
159 | return arg.getRange();
|
160 | }
|
161 | static getValueRange(document, arg) {
|
162 | return vscode_languageserver_types_1.Range.create(document.positionAt(document.offsetAt(arg.getRange().start) + arg.getValue().indexOf('=') + 1), document.positionAt(document.offsetAt(arg.getRange().end)));
|
163 | }
|
164 | /**
|
165 | * Returns the actual value of this key-value pair. The value will
|
166 | * have its escape characters removed if applicable. If the value
|
167 | * spans multiple lines and there are comments nested within the
|
168 | * lines, they too will be removed.
|
169 | *
|
170 | * @return the value that this key-value pair will actually be, may
|
171 | * be null if no value is defined, may be the empty string
|
172 | * if the value only consists of whitespace
|
173 | */
|
174 | static getValue(value, escapeChar) {
|
175 | let escaped = false;
|
176 | const skip = util_1.Util.findLeadingNonWhitespace(value, escapeChar);
|
177 | if (skip !== 0 && value.charAt(skip) === '#') {
|
178 | // need to skip over comments
|
179 | escaped = true;
|
180 | }
|
181 | value = value.substring(skip);
|
182 | let first = value.charAt(0);
|
183 | let last = value.charAt(value.length - 1);
|
184 | let literal = first === '\'' || first === '"';
|
185 | let inSingle = (first === '\'' && last === '\'');
|
186 | let inDouble = false;
|
187 | if (first === '"') {
|
188 | for (let i = 1; i < value.length; i++) {
|
189 | if (value.charAt(i) === escapeChar) {
|
190 | i++;
|
191 | }
|
192 | else if (value.charAt(i) === '"' && i === value.length - 1) {
|
193 | inDouble = true;
|
194 | }
|
195 | }
|
196 | }
|
197 | if (inSingle || inDouble) {
|
198 | value = value.substring(1, value.length - 1);
|
199 | }
|
200 | let commentCheck = -1;
|
201 | let escapedValue = "";
|
202 | let start = 0;
|
203 | parseValue: for (let i = 0; i < value.length; i++) {
|
204 | let char = value.charAt(i);
|
205 | switch (char) {
|
206 | case escapeChar:
|
207 | if (i + 1 === value.length) {
|
208 | escapedValue = escapedValue + escapeChar;
|
209 | break parseValue;
|
210 | }
|
211 | char = value.charAt(i + 1);
|
212 | if (char === ' ' || char === '\t') {
|
213 | whitespaceCheck: for (let j = i + 2; j < value.length; j++) {
|
214 | let char2 = value.charAt(j);
|
215 | switch (char2) {
|
216 | case ' ':
|
217 | case '\t':
|
218 | break;
|
219 | case '\r':
|
220 | j++;
|
221 | case '\n':
|
222 | escaped = true;
|
223 | i = j;
|
224 | continue parseValue;
|
225 | default:
|
226 | if (!inDouble && !inSingle && !literal) {
|
227 | if (char2 === escapeChar) {
|
228 | // add the escaped character
|
229 | escapedValue = escapedValue + char;
|
230 | // now start parsing from the next escape character
|
231 | i = i + 1;
|
232 | }
|
233 | else {
|
234 | // the expectation is that this j = i + 2 here
|
235 | escapedValue = escapedValue + char + char2;
|
236 | i = j;
|
237 | }
|
238 | continue parseValue;
|
239 | }
|
240 | break whitespaceCheck;
|
241 | }
|
242 | }
|
243 | }
|
244 | if (inDouble) {
|
245 | if (char === '\r') {
|
246 | escaped = true;
|
247 | i = i + 2;
|
248 | }
|
249 | else if (char === '\n') {
|
250 | escaped = true;
|
251 | i++;
|
252 | }
|
253 | else if (char !== '"') {
|
254 | if (char === escapeChar) {
|
255 | i++;
|
256 | }
|
257 | escapedValue = escapedValue + escapeChar;
|
258 | }
|
259 | continue parseValue;
|
260 | }
|
261 | else if (inSingle || literal) {
|
262 | if (char === '\r') {
|
263 | escaped = true;
|
264 | i = i + 2;
|
265 | }
|
266 | else if (char === '\n') {
|
267 | escaped = true;
|
268 | i++;
|
269 | }
|
270 | else {
|
271 | escapedValue = escapedValue + escapeChar;
|
272 | }
|
273 | continue parseValue;
|
274 | }
|
275 | else if (char === escapeChar) {
|
276 | // double escape, append one and move on
|
277 | escapedValue = escapedValue + escapeChar;
|
278 | i++;
|
279 | }
|
280 | else if (char === '\r') {
|
281 | escaped = true;
|
282 | // offset one more for \r\n
|
283 | i = i + 2;
|
284 | }
|
285 | else if (char === '\n') {
|
286 | escaped = true;
|
287 | i++;
|
288 | start = i;
|
289 | }
|
290 | else {
|
291 | // any other escapes are simply ignored
|
292 | escapedValue = escapedValue + char;
|
293 | i++;
|
294 | }
|
295 | break;
|
296 | case ' ':
|
297 | case '\t':
|
298 | if (escaped && commentCheck === -1) {
|
299 | commentCheck = i;
|
300 | }
|
301 | escapedValue = escapedValue + char;
|
302 | break;
|
303 | case '\r':
|
304 | i++;
|
305 | case '\n':
|
306 | if (escaped && commentCheck !== -1) {
|
307 | // rollback and remove the whitespace that was previously appended
|
308 | escapedValue = escapedValue.substring(0, escapedValue.length - (i - commentCheck - 1));
|
309 | commentCheck = -1;
|
310 | }
|
311 | break;
|
312 | case '#':
|
313 | // a newline was escaped and now there's a comment
|
314 | if (escaped) {
|
315 | if (commentCheck !== -1) {
|
316 | // rollback and remove the whitespace that was previously appended
|
317 | escapedValue = escapedValue.substring(0, escapedValue.length - (i - commentCheck));
|
318 | commentCheck = -1;
|
319 | }
|
320 | newlineCheck: for (let j = i + 1; j < value.length; j++) {
|
321 | switch (value.charAt(j)) {
|
322 | case '\r':
|
323 | j++;
|
324 | case '\n':
|
325 | i = j;
|
326 | break newlineCheck;
|
327 | }
|
328 | }
|
329 | continue parseValue;
|
330 | }
|
331 | default:
|
332 | if (escaped) {
|
333 | escaped = false;
|
334 | commentCheck = -1;
|
335 | }
|
336 | escapedValue = escapedValue + char;
|
337 | break;
|
338 | }
|
339 | }
|
340 | return escapedValue;
|
341 | }
|
342 | }
|
343 | exports.Property = Property;
|