UNPKG

dockerfile-ast

Version:

Parse a Dockerfile into an array of instructions and comments.

233 lines (232 loc) 9.74 kB
/* -------------------------------------------------------------------------------------------- * Copyright (c) Remy Suen. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.Dockerfile = void 0; const vscode_languageserver_types_1 = require("vscode-languageserver-types"); const ast = require("./main"); const imageTemplate_1 = require("./imageTemplate"); const from_1 = require("./instructions/from"); const util_1 = require("./util"); const main_1 = require("./main"); class Dockerfile extends imageTemplate_1.ImageTemplate { constructor(document) { super(); this.initialInstructions = new imageTemplate_1.ImageTemplate(); this.buildStages = []; this.directives = []; /** * Whether a FROM instruction has been added to this Dockerfile or not. */ this.foundFrom = false; this.document = document; } getEscapeCharacter() { for (const directive of this.directives) { if (directive.getDirective() === ast.Directive.escape) { const value = directive.getValue(); if (value === '\\' || value === '`') { return value; } } } return '\\'; } getInitialARGs() { return this.initialInstructions.getARGs(); } getContainingImage(position) { let range = vscode_languageserver_types_1.Range.create(vscode_languageserver_types_1.Position.create(0, 0), this.document.positionAt(this.document.getText().length)); if (!util_1.Util.isInsideRange(position, range)) { // not inside the document, invalid position return null; } if (this.initialInstructions.getComments().length > 0 || this.initialInstructions.getInstructions().length > 0) { if (util_1.Util.isInsideRange(position, this.initialInstructions.getRange())) { return this.initialInstructions; } } for (const buildStage of this.buildStages) { if (util_1.Util.isInsideRange(position, buildStage.getRange())) { return buildStage; } } return this; } addInstruction(instruction) { if (instruction.getKeyword() === main_1.Keyword.FROM) { this.currentBuildStage = new imageTemplate_1.ImageTemplate(); this.buildStages.push(this.currentBuildStage); this.foundFrom = true; } else if (!this.foundFrom) { this.initialInstructions.addInstruction(instruction); } if (this.foundFrom) { this.currentBuildStage.addInstruction(instruction); } super.addInstruction(instruction); } setDirectives(directives) { this.directives = directives; } getDirective() { return this.directives.length === 0 ? null : this.directives[0]; } getDirectives() { return this.directives; } resolveVariable(variable, line) { for (let from of this.getFROMs()) { let range = from.getRange(); if (range.start.line <= line && line <= range.end.line) { // resolve the FROM variable against the initial ARGs let initialARGs = new imageTemplate_1.ImageTemplate(); for (let instruction of this.initialInstructions.getARGs()) { initialARGs.addInstruction(instruction); } return initialARGs.resolveVariable(variable, line); } } let image = this.getContainingImage(vscode_languageserver_types_1.Position.create(line, 0)); if (image === null) { return undefined; } let resolvedVariable = image.resolveVariable(variable, line); if (resolvedVariable === null) { // refers to an uninitialized ARG variable, // try resolving it against the initial ARGs then let initialARGs = new imageTemplate_1.ImageTemplate(); for (let instruction of this.initialInstructions.getARGs()) { initialARGs.addInstruction(instruction); } return initialARGs.resolveVariable(variable, line); } return resolvedVariable; } getAvailableVariables(currentLine) { if (this.getInstructionAt(currentLine) instanceof from_1.From) { let variables = []; for (let arg of this.getInitialARGs()) { let property = arg.getProperty(); if (property) { variables.push(property.getName()); } } return variables; } let image = this.getContainingImage(vscode_languageserver_types_1.Position.create(currentLine, 0)); return image ? image.getAvailableVariables(currentLine) : []; } getParentStage(image) { const templateFrom = image.getFROM(); const imageName = templateFrom === null ? null : templateFrom.getImageName(); if (imageName === null) { return null; } for (const from of this.getFROMs()) { if (from.getBuildStage() === imageName) { const range = from.getRange(); // on the same line then it's an image that shares the name as the build stage if (range.start.line === templateFrom.getRange().start.line) { return null; } return this.getContainingImage(range.start); } } return null; } getStageHierarchy(line) { const image = this.getContainingImage(vscode_languageserver_types_1.Position.create(line, 0)); if (image === null) { return []; } const stages = [image]; let stage = this.getParentStage(image); while (stage !== null) { stages.splice(0, 0, stage); stage = this.getParentStage(stage); } return stages; } getAvailableWorkingDirectories(line) { const availableDirectories = new Set(); for (const image of this.getStageHierarchy(line)) { for (const workdir of image.getWORKDIRs()) { if (workdir.getRange().end.line < line) { let directory = workdir.getAbsolutePath(); if (directory !== undefined && directory !== null) { if (!directory.endsWith("/")) { directory += "/"; } availableDirectories.add(directory); } } } } return Array.from(availableDirectories); } /** * Internally reorganize the comments in the Dockerfile and allocate * them to the relevant build stages that they belong to. */ organizeComments() { const comments = this.getComments(); for (let i = 0; i < comments.length; i++) { if (util_1.Util.isInsideRange(comments[i].getRange().end, this.initialInstructions.getRange())) { this.initialInstructions.addComment(comments[i]); } else { for (const buildStage of this.buildStages) { if (util_1.Util.isInsideRange(comments[i].getRange().start, buildStage.getRange())) { buildStage.addComment(comments[i]); } } } } } getRange() { const comments = this.getComments(); const instructions = this.getInstructions(); let range = null; if (comments.length === 0) { if (instructions.length > 0) { range = vscode_languageserver_types_1.Range.create(instructions[0].getRange().start, instructions[instructions.length - 1].getRange().end); } } else if (instructions.length === 0) { range = vscode_languageserver_types_1.Range.create(comments[0].getRange().start, comments[comments.length - 1].getRange().end); } else { const commentStart = comments[0].getRange().start; const commentEnd = comments[comments.length - 1].getRange().end; const instructionStart = instructions[0].getRange().start; const instructionEnd = instructions[instructions.length - 1].getRange().end; if (commentStart.line < instructionStart.line) { if (commentEnd.line < instructionEnd.line) { range = vscode_languageserver_types_1.Range.create(commentStart, instructionEnd); } range = vscode_languageserver_types_1.Range.create(commentStart, commentEnd); } else if (commentEnd.line < instructionEnd.line) { range = vscode_languageserver_types_1.Range.create(instructionStart, instructionEnd); } else { range = vscode_languageserver_types_1.Range.create(instructionStart, commentEnd); } } if (range === null) { if (this.directives.length === 0) { return null; } return this.directives[0].getRange(); } else if (this.directives.length === 0) { return range; } return vscode_languageserver_types_1.Range.create(this.directives[0].getRange().start, range.end); } } exports.Dockerfile = Dockerfile;