UNPKG

dockerfile-ast

Version:

Parse a Dockerfile into an array of instructions and comments.

341 lines (340 loc) 14.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Property = void 0; const vscode_languageserver_types_1 = require("vscode-languageserver-types"); const util_1 = require("./util"); class Property { constructor(document, escapeChar, arg, arg2) { this.assignmentOperatorRange = null; this.assignmentOperator = null; this.valueRange = null; this.value = null; this.document = document; this.escapeChar = escapeChar; this.nameRange = Property.getNameRange(document, arg); let value = document.getText().substring(document.offsetAt(this.nameRange.start), document.offsetAt(this.nameRange.end)); this.name = Property.getValue(value, escapeChar); if (arg2) { this.valueRange = arg2.getRange(); value = document.getText().substring(document.offsetAt(this.valueRange.start), document.offsetAt(this.valueRange.end)); this.value = Property.getValue(value, escapeChar); this.range = vscode_languageserver_types_1.Range.create(this.nameRange.start, this.valueRange.end); } else { let argRange = arg.getRange(); if (this.nameRange.start.line === argRange.start.line && this.nameRange.start.character === argRange.start.character && this.nameRange.end.line === argRange.end.line && this.nameRange.end.character === argRange.end.character) { } else { this.valueRange = Property.getValueRange(document, arg); value = document.getText().substring(document.offsetAt(this.valueRange.start), document.offsetAt(this.valueRange.end)); this.value = Property.getValue(value, escapeChar); this.assignmentOperatorRange = vscode_languageserver_types_1.Range.create(this.nameRange.end, this.valueRange.start); this.assignmentOperator = "="; } this.range = argRange; } } getRange() { return this.range; } getName() { return this.name; } getNameRange() { return this.nameRange; } getValue() { return this.value; } getValueRange() { return this.valueRange; } /** * Retrieves the operator used for delimiting between the name and * value of this property. This will either be the "=" character * or null if a character was not used or if this property has no * value defined. */ getAssignmentOperator() { return this.assignmentOperator; } getAssignmentOperatorRange() { return this.assignmentOperatorRange; } /** * Returns the value of this property including any enclosing * single or double quotes and relevant escape characters. * Escaped newlines and its associated contiguous whitespace * characters however will not be returned as they are deemed to * be uninteresting to clients trying to return a Dockerfile. * * @return the unescaped value of this property or null if this * property has no associated value */ getUnescapedValue() { if (this.valueRange === null) { return null; } let escaped = false; let rawValue = ""; let value = this.document.getText().substring(this.document.offsetAt(this.valueRange.start), this.document.offsetAt(this.valueRange.end)); rawLoop: for (let i = 0; i < value.length; i++) { let char = value.charAt(i); switch (char) { case this.escapeChar: for (let j = i + 1; j < value.length; j++) { switch (value.charAt(j)) { case '\r': j++; case '\n': escaped = true; i = j; continue rawLoop; case ' ': case '\t': break; default: rawValue = rawValue + char; continue rawLoop; } } // this happens if there's only whitespace after the escape character rawValue = rawValue + char; break; case '\r': case '\n': break; case ' ': case '\t': if (!escaped) { rawValue = rawValue + char; } break; case '#': if (escaped) { for (let j = i + 1; j < value.length; j++) { switch (value.charAt(j)) { case '\r': j++; case '\n': i = j; continue rawLoop; } } } else { rawValue = rawValue + char; } break; default: rawValue = rawValue + char; escaped = false; break; } } return rawValue; } static getNameRange(document, arg) { let value = arg.getValue(); let index = value.indexOf('='); if (index !== -1) { let initial = value.charAt(0); let before = value.charAt(index - 1); // check if content before the equals sign are in quotes // "var"=value // 'var'=value // otherwise, just assume it's a standard definition // var=value if ((initial === '"' && before === '"') || (initial === '\'' && before === '\'') || (initial !== '"' && initial !== '\'')) { return vscode_languageserver_types_1.Range.create(arg.getRange().start, document.positionAt(document.offsetAt(arg.getRange().start) + index)); } } // no '=' found, just defined the property's name return arg.getRange(); } static getValueRange(document, arg) { 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))); } /** * Returns the actual value of this key-value pair. The value will * have its escape characters removed if applicable. If the value * spans multiple lines and there are comments nested within the * lines, they too will be removed. * * @return the value that this key-value pair will actually be, may * be null if no value is defined, may be the empty string * if the value only consists of whitespace */ static getValue(value, escapeChar) { let escaped = false; const skip = util_1.Util.findLeadingNonWhitespace(value, escapeChar); if (skip !== 0 && value.charAt(skip) === '#') { // need to skip over comments escaped = true; } value = value.substring(skip); let first = value.charAt(0); let last = value.charAt(value.length - 1); let literal = first === '\'' || first === '"'; let inSingle = (first === '\'' && last === '\''); let inDouble = false; if (first === '"') { for (let i = 1; i < value.length; i++) { if (value.charAt(i) === escapeChar) { i++; } else if (value.charAt(i) === '"' && i === value.length - 1) { inDouble = true; } } } if (inSingle || inDouble) { value = value.substring(1, value.length - 1); } let commentCheck = -1; let escapedValue = ""; let start = 0; parseValue: for (let i = 0; i < value.length; i++) { let char = value.charAt(i); switch (char) { case escapeChar: if (i + 1 === value.length) { escapedValue = escapedValue + escapeChar; break parseValue; } char = value.charAt(i + 1); if (char === ' ' || char === '\t') { whitespaceCheck: for (let j = i + 2; j < value.length; j++) { let char2 = value.charAt(j); switch (char2) { case ' ': case '\t': break; case '\r': j++; case '\n': escaped = true; i = j; continue parseValue; default: if (!inDouble && !inSingle && !literal) { if (char2 === escapeChar) { // add the escaped character escapedValue = escapedValue + char; // now start parsing from the next escape character i = i + 1; } else { // the expectation is that this j = i + 2 here escapedValue = escapedValue + char + char2; i = j; } continue parseValue; } break whitespaceCheck; } } } if (inDouble) { if (char === '\r') { escaped = true; i = i + 2; } else if (char === '\n') { escaped = true; i++; } else if (char !== '"') { if (char === escapeChar) { i++; } escapedValue = escapedValue + escapeChar; } continue parseValue; } else if (inSingle || literal) { if (char === '\r') { escaped = true; i = i + 2; } else if (char === '\n') { escaped = true; i++; } else { escapedValue = escapedValue + escapeChar; } continue parseValue; } else if (char === escapeChar) { // double escape, append one and move on escapedValue = escapedValue + escapeChar; i++; } else if (char === '\r') { escaped = true; // offset one more for \r\n i = i + 2; } else if (char === '\n') { escaped = true; i++; start = i; } else { // any other escapes are simply ignored escapedValue = escapedValue + char; i++; } break; case ' ': case '\t': if (escaped && commentCheck === -1) { commentCheck = i; } escapedValue = escapedValue + char; break; case '\r': i++; case '\n': if (escaped && commentCheck !== -1) { // rollback and remove the whitespace that was previously appended escapedValue = escapedValue.substring(0, escapedValue.length - (i - commentCheck - 1)); commentCheck = -1; } break; case '#': // a newline was escaped and now there's a comment if (escaped) { if (commentCheck !== -1) { // rollback and remove the whitespace that was previously appended escapedValue = escapedValue.substring(0, escapedValue.length - (i - commentCheck)); commentCheck = -1; } newlineCheck: for (let j = i + 1; j < value.length; j++) { switch (value.charAt(j)) { case '\r': j++; case '\n': i = j; break newlineCheck; } } continue parseValue; } default: if (escaped) { escaped = false; commentCheck = -1; } escapedValue = escapedValue + char; break; } } return escapedValue; } } exports.Property = Property;