• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rcjsuen / dockerfile-utils / 9573633355

18 Jun 2024 11:33PM UTC coverage: 99.422% (+0.3%) from 99.117%
9573633355

push

github

rcjsuen
Update nyc to 17.x

This should resolve the memory issues that we have been facing recently.

Signed-off-by: Remy Suen <remy.suen@gmail.com>

477 of 481 branches covered (99.17%)

Branch coverage included in aggregate %.

1070 of 1075 relevant lines covered (99.53%)

769.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.54
/src/dockerValidator.ts
1
/* --------------------------------------------------------------------------------------------
2
 * Copyright (c) Remy Suen. All rights reserved.
3
 * Licensed under the MIT License. See License.txt in the project root for license information.
4
 * ------------------------------------------------------------------------------------------ */
5
import { TextDocument } from 'vscode-languageserver-textdocument';
6
import { Diagnostic, DiagnosticSeverity, DiagnosticTag, Position, Range, uinteger } from 'vscode-languageserver-types';
1✔
7
import { Dockerfile, Flag, Instruction, JSONInstruction, Add, Arg, Cmd, Copy, Entrypoint, From, Healthcheck, Onbuild, ModifiableInstruction, PropertyInstruction, Property, DockerfileParser, Directive, Keyword, Argument, Run } from 'dockerfile-ast';
1✔
8
import { ValidationCode, ValidationSeverity, ValidatorSettings } from './main';
1✔
9

10
export const KEYWORDS = [
1✔
11
    "ADD",
12
    "ARG",
13
    "CMD",
14
    "COPY",
15
    "ENTRYPOINT",
16
    "ENV",
17
    "EXPOSE",
18
    "FROM",
19
    "HEALTHCHECK",
20
    "LABEL",
21
    "MAINTAINER",
22
    "ONBUILD",
23
    "RUN",
24
    "SHELL",
25
    "STOPSIGNAL",
26
    "USER",
27
    "VOLUME",
28
    "WORKDIR"
29
];
30

31
export interface DockerfileDiagnostic extends Diagnostic {
32

33
    /**
34
     * Position of the instruction position in a document (zero-based).
35
     * This may be null if the diagnostic is not for an instruction (for
36
     * example, the diagnostic may be for a parser directive).
37
     */
38
    instructionLine: uinteger | null;
39
}
40

41
/**
42
 * A list of variables that are prepopulated when using the BuildKit
43
 * backend.
44
 */
45
const PREDEFINED_VARIABLES = [
1✔
46
    "TARGETPLATFORM",
47
    "TARGETOS",
48
    "TARGETARCH",
49
    "TARGETVARIANT",
50
    "BUILDPLATFORM",
51
    "BUILDOS",
52
    "BUILDARCH",
53
    "BUILDVARIAN"
54
];
55

56
export class Validator {
1✔
57

58
    private document: TextDocument;
59

60
    private settings: ValidatorSettings = {
5,726✔
61
        deprecatedMaintainer: ValidationSeverity.WARNING,
62
        directiveCasing: ValidationSeverity.WARNING,
63
        emptyContinuationLine: ValidationSeverity.WARNING,
64
        instructionCasing: ValidationSeverity.WARNING,
65
        instructionCmdMultiple: ValidationSeverity.WARNING,
66
        instructionEntrypointMultiple: ValidationSeverity.WARNING,
67
        instructionHealthcheckMultiple: ValidationSeverity.WARNING,
68
        instructionJSONInSingleQuotes: ValidationSeverity.WARNING,
69
        instructionWorkdirRelative: ValidationSeverity.WARNING
70
    }
71

72
    constructor(settings?: ValidatorSettings) {
73
        if (settings) {
5,726✔
74
            this.settings = settings;
5,672✔
75
        }
76
    }
77

78
    private checkDirectives(dockerfile: Dockerfile, problems: Diagnostic[]) {
79
        const duplicatedEscapes = [];
5,730✔
80
        for (const directive of dockerfile.getDirectives()) {
5,730✔
81
            if (directive.getDirective() === Directive.escape) {
49✔
82
                duplicatedEscapes.push(directive);
46✔
83
            }
84
        }
85

86
        if (duplicatedEscapes.length > 1) {
5,730✔
87
            // multiple escape parser directives have been found
88
            for (let i = 1; i < duplicatedEscapes.length; i++) {
1✔
89
                problems.push(Validator.createDuplicatedEscapeDirective(duplicatedEscapes[i].getNameRange().start, duplicatedEscapes[i].getValueRange().end));
1✔
90
            }
91
            return
1✔
92
        }
93

94
        for (const directive of dockerfile.getDirectives()) {
5,729✔
95
            const directiveName = directive.getDirective();
47✔
96
            if (directiveName === Directive.escape) {
47✔
97
                const value = directive.getValue();
44✔
98
                if (value !== '\\' && value !== '`' && value !== "") {
44✔
99
                    // if the directive's value is invalid or isn't the empty string, flag it
100
                    const range = directive.getValueRange();
4✔
101
                    problems.push(Validator.createInvalidEscapeDirective(range.start, range.end, value));
4✔
102
                }
103
    
104
                if (directive.getName() !== Directive.escape) {
44✔
105
                    const range = directive.getNameRange();
6✔
106
                    const diagnostic = this.createLowercaseDirective(range.start, range.end);
6✔
107
                    if (diagnostic) {
6✔
108
                        problems.push(diagnostic);
5✔
109
                    }
110
                }
111
            }
112
        }
113
    }
114

115
    /**
116
     * Checks the arguments of the given instruction.
117
     * 
118
     * @param instruction the instruction to validate
119
     * @param problems an array of identified problems in the document
120
     * @param expectedArgCount an array of expected number of arguments
121
     *                         for the instruction, if its length is 1
122
     *                         and its value is -1, any number of
123
     *                         arguments greater than zero is valid
124
     * @param validate the function to use to validate an argument
125
     * @param createIncompleteDiagnostic the function to use to create a diagnostic
126
     *                                   if the number of arguments is incorrect
127
     */
128
    private checkArguments(instruction: Instruction, problems: Diagnostic[], expectedArgCount: number[],
129
        validate: Function, createIncompleteDiagnostic?: Function): void {
130
        let args = instruction instanceof PropertyInstruction ? instruction.getPropertyArguments() : instruction.getArguments();
10,289✔
131
        if (args.length === 0) {
10,289✔
132
            if (instruction.getKeyword() !== Keyword.RUN) {
656✔
133
                // all instructions are expected to have at least one argument
134
                const range = instruction.getInstructionRange();
602✔
135
                problems.push(Validator.createMissingArgument(range.start.line, range.start, range.end));
602✔
136
            }
137
        } else if (expectedArgCount[0] === -1) {
9,633✔
138
            for (let i = 0; i < args.length; i++) {
3,695✔
139
                let createInvalidDiagnostic = validate(i, args[i].getValue(), args[i].getRange());
4,249✔
140
                if (createInvalidDiagnostic) {
4,249!
141
                    let range = args[i].getRange();
×
142
                    problems.push(createInvalidDiagnostic(instruction.getInstructionRange().start.line, range.start, range.end, args[i].getValue()));
×
143
                }
144
            }
145
        } else {
146
            for (let i = 0; i < expectedArgCount.length; i++) {
5,938✔
147
                if (expectedArgCount[i] === args.length) {
5,987✔
148
                    for (let j = 0; j < args.length; j++) {
5,916✔
149
                        let range = args[j].getRange();
5,986✔
150
                        let createInvalidDiagnostic = validate(j, args[j].getValue(), range);
5,986✔
151
                        if (createInvalidDiagnostic instanceof Function) {
5,986✔
152
                            problems.push(createInvalidDiagnostic(instruction.getInstructionRange().start.line, range.start, range.end, args[j].getValue()));
5✔
153
                        } else if (createInvalidDiagnostic !== null) {
5,981✔
154
                            problems.push(createInvalidDiagnostic);
48✔
155
                        }
156
                    }
157
                    return;
5,916✔
158
                }
159
            }
160

161
            let range = args[args.length - 1].getRange();
22✔
162
            if (createIncompleteDiagnostic) {
22✔
163
                problems.push(createIncompleteDiagnostic(instruction.getInstructionRange().start.line, range.start, range.end));
14✔
164
            } else {
165
                problems.push(Validator.createExtraArgument(instruction.getInstructionRange().start.line, range.start, range.end));
8✔
166
            }
167
        }
168
    }
169

170
    private checkVariables(instruction: Instruction, problems: Diagnostic[]): void {
171
        for (let variable of instruction.getVariables()) {
11,528✔
172
            let modifier = variable.getModifier();
221✔
173
            if (modifier !== null) {
221✔
174
                switch (instruction.getKeyword()) {
118✔
175
                    case Keyword.CMD:
136✔
176
                    case Keyword.ENTRYPOINT:
177
                    case Keyword.RUN:
178
                        // allow shell expansions to go through for RUN instructions
179
                        break;
18✔
180
                    default:
181
                        if (modifier === "") {
100✔
182
                            problems.push(Validator.createVariableUnsupportedModifier(instruction.getRange().start.line, variable.getRange(), variable.toString(), modifier));
15✔
183
                        } else if (modifier !== '+' && modifier !== '-' && modifier !== '?') {
85✔
184
                            problems.push(Validator.createVariableUnsupportedModifier(instruction.getRange().start.line, variable.getModifierRange(), variable.toString(), modifier));
30✔
185
                        }
186
                        break;
100✔
187
                }
188
            }
189
        }
190
    }
191

192
    private checkProperty(document: TextDocument, escapeChar: string, keyword: string, instructionLine: uinteger, property: Property, firstProperty: boolean, optionalValue: boolean, problems: Diagnostic[]): void {
193
        let name = property.getName();
3,102✔
194
        if (name === "") {
3,102✔
195
            let range = property.getRange();
10✔
196
            problems.push(Validator.createSyntaxMissingNames(instructionLine, range.start, range.end, keyword));
10✔
197
        } else if (name.indexOf('=') !== -1) {
3,092✔
198
            let nameRange = property.getNameRange();
12✔
199
            let unescapedName = document.getText(nameRange);
12✔
200
            let index = unescapedName.indexOf('=');
12✔
201
            if (unescapedName.charAt(0) === '\'') {
12✔
202
                problems.push(Validator.createSyntaxMissingSingleQuote(instructionLine, nameRange.start, document.positionAt(document.offsetAt(nameRange.start) + index), unescapedName.substring(0, unescapedName.indexOf('='))));
6✔
203
            } else if (unescapedName.charAt(0) === '"') {
6✔
204
                problems.push(Validator.createSyntaxMissingDoubleQuote(instructionLine, nameRange.start, document.positionAt(document.offsetAt(nameRange.start) + index), unescapedName.substring(0, unescapedName.indexOf('='))));
6✔
205
            }
206
            return;
12✔
207
        }
208

209
        let value = property.getValue();
3,090✔
210
        if (value === null) {
3,090✔
211
            if (!optionalValue) {
35✔
212
                let range = property.getNameRange();
6✔
213
                if (firstProperty) {
6✔
214
                    problems.push(Validator.createENVRequiresTwoArguments(instructionLine, range.start, range.end));
2✔
215
                } else {
216
                    problems.push(Validator.createSyntaxMissingEquals(instructionLine, range.start, range.end, name));
4✔
217
                }
218
            }
219
        } else if (value.charAt(0) === '"') {
3,055✔
220
            let found = false;
24✔
221
            for (let i = 1; i < value.length; i++) {
24✔
222
                switch (value.charAt(i)) {
102✔
223
                    case escapeChar:
15✔
224
                        i++;
12✔
225
                        break;
12✔
226
                    case '"':
227
                        if (i === value.length - 1) {
3✔
228
                            found = true;
3✔
229
                        }
230
                        break;
3✔
231
                }
232
            }
233

234
            if (!found) {
24✔
235
                let range = property.getValueRange();
21✔
236
                problems.push(Validator.createSyntaxMissingDoubleQuote(instructionLine, range.start, range.end, property.getUnescapedValue()));
21✔
237
            }
238
        } else if (value.charAt(0) === '\'' && value.charAt(value.length - 1) !== '\'') {
3,031✔
239
            let range = property.getValueRange();
6✔
240
            problems.push(Validator.createSyntaxMissingSingleQuote(instructionLine, range.start, range.end, value));
6✔
241
        }
242
    }
243

244
    validate(document: TextDocument): Diagnostic[] {
245
        this.document = document;
5,730✔
246
        let problems: DockerfileDiagnostic[] = [];
5,730✔
247
        let dockerfile = DockerfileParser.parse(document.getText());
5,730✔
248
        this.checkDirectives(dockerfile, problems);
5,730✔
249
        let instructions = dockerfile.getInstructions();
5,730✔
250
        if (instructions.length === 0 || dockerfile.getARGs().length === instructions.length) {
5,730✔
251
            // no instructions in this file, or only ARGs
252
            problems.push(Validator.createNoSourceImage(document.positionAt(0), document.positionAt(0)));
11✔
253
        }
254

255
        let cmds: Cmd[] = [];
5,730✔
256
        let entrypoints: Entrypoint[] = [];
5,730✔
257
        let healthchecks: Healthcheck[] = [];
5,730✔
258
        for (let instruction of instructions) {
5,730✔
259
            if (instruction instanceof Cmd) {
11,528✔
260
                cmds.push(instruction);
112✔
261
            } else if (instruction instanceof Entrypoint) {
11,416✔
262
                entrypoints.push(instruction);
97✔
263
            } else if (instruction instanceof Healthcheck) {
11,319✔
264
                healthchecks.push(instruction);
259✔
265
            } else if (instruction instanceof From) {
11,060✔
266
                this.createDuplicatesDiagnostics(problems, this.settings.instructionCmdMultiple, "CMD", cmds);
5,821✔
267
                this.createDuplicatesDiagnostics(problems, this.settings.instructionEntrypointMultiple, "ENTRYPOINT", entrypoints);
5,821✔
268
                this.createDuplicatesDiagnostics(problems, this.settings.instructionHealthcheckMultiple, "HEALTHCHECK", healthchecks);
5,821✔
269

270
                cmds = [];
5,821✔
271
                entrypoints = [];
5,821✔
272
                healthchecks = [];
5,821✔
273
            }
274
        }
275
        this.createDuplicatesDiagnostics(problems, this.settings.instructionCmdMultiple, "CMD", cmds);
5,730✔
276
        this.createDuplicatesDiagnostics(problems, this.settings.instructionEntrypointMultiple, "ENTRYPOINT", entrypoints);
5,730✔
277
        this.createDuplicatesDiagnostics(problems, this.settings.instructionHealthcheckMultiple, "HEALTHCHECK", healthchecks);
5,730✔
278
        this.createDuplicateBuildStageNameDiagnostics(problems, dockerfile.getFROMs());
5,730✔
279

280
        let escapeChar = dockerfile.getEscapeCharacter();
5,730✔
281
        let hasFrom = false;
5,730✔
282
        for (let instruction of dockerfile.getInstructions()) {
5,730✔
283
            let keyword = instruction.getKeyword();
11,528✔
284
            if (keyword === "FROM") {
11,528✔
285
                hasFrom = true;
5,821✔
286
            } else if (!hasFrom && keyword !== "ARG") {
5,707✔
287
                // first non-ARG instruction is not a FROM
288
                let range = instruction.getInstructionRange();
6✔
289
                problems.push(Validator.createNoSourceImage(range.start, range.end));
6✔
290
                hasFrom = true;
6✔
291
            }
292
            this.validateInstruction(document, escapeChar, instruction, keyword, false, problems);
11,528✔
293
            this.checkVariables(instruction, problems);
11,528✔
294
        }
295

296
        for (let instruction of dockerfile.getOnbuildTriggers()) {
5,730✔
297
            this.validateInstruction(document, escapeChar, instruction, instruction.getKeyword(), true, problems);
98✔
298
        }
299

300
        const ignoredLines = [];
5,730✔
301
        for (const comment of dockerfile.getComments()) {
5,730✔
302
            if (comment.getContent() === "dockerfile-utils: ignore") {
80✔
303
                ignoredLines.push(comment.getRange().start.line);
3✔
304
            }
305
        }
306

307
        problemsCheck: for (let i = 0; i < problems.length; i++) {
5,730✔
308
            if (problems[i].instructionLine !== null) {
1,498✔
309
                for (const ignoredLine of ignoredLines) {
1,458✔
310
                    if (ignoredLine + 1 === problems[i].instructionLine) {
4✔
311
                        problems.splice(i, 1);
3✔
312
                        i--;
3✔
313
                        continue problemsCheck;
3✔
314
                    }
315
                }
316
            }
317
        }
318
        return problems;
5,730✔
319
    }
320

321
    /**
322
     * Retrieves the line numbers that corresponds to the content of
323
     * here-documents in the given instruction. The line numbers are
324
     * zero-based.
325
     * 
326
     * @param instruction the instruction to check
327
     * @returns an array of line numbers where content of
328
     *          here-documents are defined
329
     */
330
    private getHeredocLines(instruction: Instruction): number[] {
331
        if (instruction instanceof Copy || instruction instanceof Run) {
3,043✔
332
            const lines: number[] = [];
99✔
333
            for (const heredoc of instruction.getHeredocs()) {
99✔
334
                const range = heredoc.getContentRange();
19✔
335
                if (range !== null) {
19✔
336
                    for (let i = range.start.line; i <= range.end.line; i++) {
19✔
337
                        lines.push(i);
25✔
338
                    }
339
                 }
340
            }
341
            return lines;
99✔
342
        }
343
        return []
2,944✔
344
    }
345

346
    private validateInstruction(document: TextDocument, escapeChar: string, instruction: Instruction, keyword: string, isTrigger: boolean, problems: Diagnostic[]): void {
347
        if (KEYWORDS.indexOf(keyword) === -1) {
11,626✔
348
            let range = instruction.getInstructionRange();
22✔
349
            // invalid instruction found
350
            problems.push(Validator.createUnknownInstruction(range.start.line, range.start, range.end, keyword));
22✔
351
        } else {
352
            if (keyword !== instruction.getInstruction()) {
11,604✔
353
                let range = instruction.getInstructionRange();
85✔
354
                // warn about uppercase convention if the keyword doesn't match the actual instruction
355
                let diagnostic = this.createUppercaseInstruction(range.start.line, range.start, range.end);
85✔
356
                if (diagnostic) {
85✔
357
                    problems.push(diagnostic);
80✔
358
                }
359
            }
360

361
            if (keyword === "MAINTAINER") {
11,604✔
362
                let range = instruction.getInstructionRange();
82✔
363
                let diagnostic = this.createMaintainerDeprecated(range.start.line, range.start, range.end);
82✔
364
                if (diagnostic) {
82✔
365
                    problems.push(diagnostic);
6✔
366
                }
367
            }
368

369
            const fullRange = instruction.getRange();
11,604✔
370
            if (fullRange.start.line !== fullRange.end.line && !isTrigger) {
11,604✔
371
                // if the instruction spans multiple lines, check for empty newlines
372
                const content = document.getText();
3,043✔
373
                const endingLine = fullRange.end.line;
3,043✔
374
                const skippedLines = this.getHeredocLines(instruction);
3,043✔
375
                let skipIndex = 0;
3,043✔
376
                let start = -1;
3,043✔
377
                for (let i = fullRange.start.line; i <= endingLine; i++) {
3,043✔
378
                    if (i === skippedLines[skipIndex]) {
6,850✔
379
                        skipIndex++;
25✔
380
                        continue;
25✔
381
                    }
382
                    const lineContent = content.substring(document.offsetAt(Position.create(i, 0)), document.offsetAt(Position.create(i + 1, 0)));
6,825✔
383
                    if (lineContent.trim().length === 0) {
6,825✔
384
                        if (start === -1) {
529✔
385
                            start = i;
523✔
386
                            continue;
523✔
387
                        }
388
                    } else if (start !== -1) {
6,296✔
389
                        const diagnostic = Validator.createEmptyContinuationLine(Position.create(start, 0), Position.create(i, 0), this.settings.emptyContinuationLine);
58✔
390
                        if (diagnostic) {
58✔
391
                            problems.push(diagnostic);
11✔
392
                        }
393
                        start = -1;
58✔
394
                    }
395
                }
396

397
                if (start !== -1) {
3,043✔
398
                    const diagnostic = Validator.createEmptyContinuationLine(Position.create(start, 0), Position.create(endingLine + 1, 0), this.settings.emptyContinuationLine);
465✔
399
                    if (diagnostic) {
465✔
400
                        problems.push(diagnostic);
2✔
401
                    }
402
                    start = -1;
465✔
403
                }
404
            }
405

406
            switch (keyword) {
11,604✔
407
                case "CMD":
13,091✔
408
                    this.checkJSONQuotes(instruction, problems);
123✔
409
                    break;
123✔
410
                case "ENTRYPOINT":
411
                case "RUN":
412
                case "VOLUME":
413
                    this.checkArguments(instruction, problems, [-1], function (): any {
330✔
414
                        return null;
354✔
415
                    });
416
                    this.checkJSONQuotes(instruction, problems);
330✔
417
                    break;
330✔
418
                case "ARG":
419
                    this.checkArguments(instruction, problems, [-1], () => null);
1,034✔
420
                    let arg = instruction as Arg;
1,034✔
421
                    let argProperty = arg.getProperty();
1,034✔
422
                    if (argProperty) {
1,034✔
423
                        this.checkProperty(document, escapeChar, keyword, instruction.getRange().start.line, argProperty, true, true, problems);
977✔
424
                    }
425
                    break;
1,034✔
426
                case "ENV":
427
                case "LABEL":
428
                    this.checkArguments(instruction, problems, [-1], function (): any {
2,180✔
429
                        return null;
2,128✔
430
                    });
431
                    let properties = (instruction as PropertyInstruction).getProperties();
2,180✔
432
                    if (properties.length === 1) {
2,180✔
433
                        this.checkProperty(document, escapeChar, keyword, instruction.getRange().start.line, properties[0], true, false, problems);
2,051✔
434
                    } else if (properties.length !== 0) {
129✔
435
                        for (let property of properties) {
28✔
436
                            this.checkProperty(document, escapeChar, keyword, instruction.getRange().start.line, property, false, false, problems);
74✔
437
                        }
438
                    }
439
                    break;
2,180✔
440
                case "FROM":
441
                    const fromInstructionRange = instruction.getInstructionRange();
5,826✔
442
                    const fromFlags = (instruction as ModifiableInstruction).getFlags();
5,826✔
443
                    for (const flag of fromFlags) {
5,826✔
444
                        const flagName = flag.getName();
6✔
445
                        if (flagName !== "platform") {
6✔
446
                            const range = flag.getRange();
3✔
447
                            problems.push(Validator.createUnknownFromFlag(fromInstructionRange.start.line, range.start, flagName === "" ? range.end : flag.getNameRange().end, flag.getName()));
3✔
448
                        }
449
                    }
450
                    this.checkFlagValue(fromInstructionRange.start.line, fromFlags, [ "platform"], problems);
5,826✔
451
                    this.checkArguments(instruction, problems, [1, 3], function (index: number, argument: string, range: Range): Diagnostic | Function | null {
5,826✔
452
                        switch (index) {
5,832✔
453
                            case 0:
5,832!
454
                                let variables = instruction.getVariables();
5,762✔
455
                                if (variables.length > 0) {
5,762✔
456
                                    let variableRange = variables[0].getRange();
24✔
457
                                    if (variableRange.start.line === range.start.line
24✔
458
                                            && variableRange.start.character === range.start.character
459
                                            && variableRange.end.line === range.end.line
460
                                            && variableRange.end.character === range.end.character) {
461
                                        if (!variables[0].isDefined()) {
8✔
462
                                            if (PREDEFINED_VARIABLES.indexOf(variables[0].getName()) !== -1) {
6✔
463
                                                return null;
2✔
464
                                            }
465
                                            // the '-' sign suggests a default value so even if the variable is not defined it's okay
466
                                            if (variables[0].getModifier() === '-') {
4✔
467
                                                const parameter = variables[0].getSubstitutionParameter()
2✔
468
                                                if (parameter !== "" && parameter !== null) {
2✔
469
                                                    return null;
1✔
470
                                                }
471
                                            }
472
                                            return Validator.createBaseNameEmpty(fromInstructionRange.start.line, variableRange, variables[0].toString());
3✔
473
                                        }
474
                                    }
475
                                    return null;
18✔
476
                                }
477
                                let from = instruction as From;
5,738✔
478
                                let digestRange = from.getImageDigestRange();
5,738✔
479
                                const tagRange = from.getImageTagRange();
5,738✔
480
                                if (digestRange === null) {
5,738✔
481
                                    if (tagRange === null) {
5,694✔
482
                                        return null;
5,661✔
483
                                    }
484
                                    let tag = document.getText(tagRange);
33✔
485
                                    if (tag === "") {
33✔
486
                                        // no tag specified, just highlight the whole argument
487
                                        return Validator.createInvalidReferenceFormat(fromInstructionRange.start.line, range);
1✔
488
                                    }
489
                                    let tagRegexp = new RegExp(/^[\w][\w.-]{0,127}$/);
32✔
490
                                    if (tagRegexp.test(tag)) {
32✔
491
                                        return null;
27✔
492
                                    }
493
                                    return Validator.createInvalidReferenceFormat(fromInstructionRange.start.line, from.getImageTagRange());
5✔
494
                                }
495
                                if (tagRange !== null) {
44✔
496
                                    // specified an optional tag with the digest
497
                                    if (tagRange.start.line === tagRange.end.line && tagRange.start.character === tagRange.end.character) {
2✔
498
                                        // tag is empty, flag the whole argument as an error
499
                                        return Validator.createInvalidReferenceFormat(fromInstructionRange.start.line, range);
1✔
500
                                    }
501
                                }
502
                                let digest = document.getText(digestRange);
43✔
503
                                let algorithmIndex = digest.indexOf(':');
43✔
504
                                if (algorithmIndex === -1) {
43✔
505
                                    if (digest === "") {
21✔
506
                                        // no digest specified, just highlight the whole argument
507
                                        return Validator.createInvalidReferenceFormat(fromInstructionRange.start.line, range);
7✔
508
                                    }
509
                                    return Validator.createInvalidReferenceFormat(fromInstructionRange.start.line, from.getImageDigestRange());
14✔
510
                                }
511
                                let algorithmRegexp = new RegExp(/[A-Fa-f0-9_+.-]+/);
22✔
512
                                let algorithm = digest.substring(0, algorithmIndex);
22✔
513
                                if (!algorithmRegexp.test(algorithm)) {
22✔
514
                                    return Validator.createInvalidReferenceFormat(fromInstructionRange.start.line, from.getImageDigestRange());
7✔
515
                                }
516
                                let hex = digest.substring(algorithmIndex + 1);
15✔
517
                                let hexRegexp = new RegExp(/[A-Fa-f0-9]+/);
15✔
518
                                if (hexRegexp.test(hex)) {
15✔
519
                                    return null;
8✔
520
                                }
521
                                return Validator.createInvalidReferenceFormat(fromInstructionRange.start.line, from.getImageDigestRange());
7✔
522
                            case 1:
523
                                return argument.toUpperCase() === "AS" ? null : Validator.createInvalidAs;
35✔
524
                            case 2:
525
                                argument = argument.toLowerCase();
35✔
526
                                let regexp = new RegExp(/^[a-z]([a-z0-9_\-.]*)*$/);
35✔
527
                                if (regexp.test(argument)) {
35✔
528
                                    return null;
32✔
529
                                }
530
                                return Validator.createInvalidBuildStageName(fromInstructionRange.start.line, range, argument);;
3✔
531
                            default:
532
                                return null;
×
533
                        }
534
                    }, Validator.createRequiresOneOrThreeArguments);
535
                    break;
5,826✔
536
                case "HEALTHCHECK":
537
                    let args = instruction.getArguments();
273✔
538
                    const healthcheckInstructionRange = instruction.getInstructionRange();
273✔
539
                    const healthcheckFlags = (instruction as ModifiableInstruction).getFlags();
273✔
540
                    if (args.length === 0) {
273✔
541
                        // all instructions are expected to have at least one argument
542
                        problems.push(Validator.createHEALTHCHECKRequiresAtLeastOneArgument(healthcheckInstructionRange.start.line, healthcheckInstructionRange));
53✔
543
                    } else {
544
                        const value = args[0].getValue();
220✔
545
                        const uppercase = value.toUpperCase();
220✔
546
                        if (uppercase === "NONE") {
220✔
547
                            // check that NONE doesn't have any arguments after it
548
                            if (args.length > 1) {
34✔
549
                                // get the next argument
550
                                const start = args[1].getRange().start;
2✔
551
                                // get the last argument
552
                                const end = args[args.length - 1].getRange().end;
2✔
553
                                // highlight everything after the NONE and warn the user
554
                                problems.push(Validator.createHealthcheckNoneUnnecessaryArgument(healthcheckInstructionRange.start.line, start, end));
2✔
555
                            }
556
                            // don't need to validate flags of a NONE
557
                            break;
34✔
558
                        } else if (uppercase === "CMD") {
186✔
559
                            if (args.length === 1) {
181✔
560
                                // this HEALTHCHECK has a CMD with no arguments
561
                                const range = args[0].getRange();
3✔
562
                                problems.push(Validator.createHealthcheckCmdArgumentMissing(healthcheckInstructionRange.start.line, range.start, range.end));
3✔
563
                            }
564
                        } else {
565
                            // unknown HEALTHCHECK type
566
                            problems.push(Validator.createHealthcheckTypeUnknown(healthcheckInstructionRange.start.line, args[0].getRange(), uppercase));
5✔
567
                        }
568
                    }
569

570
                    const validFlags = ["interval", "retries", "start-period", "timeout", "start-interval"];
239✔
571
                    for (const flag of healthcheckFlags) {
239✔
572
                        const flagName = flag.getName();
136✔
573
                        if (validFlags.indexOf(flagName) === -1) {
136✔
574
                            const range = flag.getRange();
13✔
575
                            problems.push(Validator.createUnknownHealthcheckFlag(healthcheckInstructionRange.start.line, range.start, flagName === "" ? range.end : flag.getNameRange().end, flag.getName()));
13✔
576
                        } else if (flagName === "retries") {
123✔
577
                            const value = flag.getValue();
10✔
578
                            if (value) {
10✔
579
                                const valueRange = flag.getValueRange();
8✔
580
                                const integer = parseInt(value);
8✔
581
                                // test for NaN or numbers with decimals
582
                                if (isNaN(integer) || value.indexOf('.') !== -1) {
8✔
583
                                    problems.push(Validator.createInvalidSyntax(healthcheckInstructionRange.start.line, valueRange.start, valueRange.end, value));
2✔
584
                                } else if (integer < 1) {
6✔
585
                                    problems.push(Validator.createFlagAtLeastOne(healthcheckInstructionRange.start.line, valueRange.start, valueRange.end, "--retries", integer.toString()));
2✔
586
                                }
587
                            }
588
                        }
589
                    }
590

591
                    this.checkFlagValue(healthcheckInstructionRange.start.line, healthcheckFlags, validFlags, problems);
239✔
592
                    this.checkFlagDuration(healthcheckInstructionRange.start.line, healthcheckFlags, ["interval", "start-period", "timeout", "start-interval"], problems);
239✔
593
                    this.checkDuplicateFlags(healthcheckInstructionRange.start.line, healthcheckFlags, validFlags, problems);
239✔
594
                    break;
239✔
595
                case "ONBUILD":
596
                    this.checkArguments(instruction, problems, [-1], function (): any {
153✔
597
                        return null;
293✔
598
                    });
599
                    let onbuild = instruction as Onbuild;
153✔
600
                    let trigger = onbuild.getTrigger();
153✔
601
                    switch (trigger) {
153✔
602
                        case "FROM":
24✔
603
                        case "MAINTAINER":
604
                            problems.push(Validator.createOnbuildTriggerDisallowed(onbuild.getInstructionRange().start.line, onbuild.getTriggerRange(), trigger));
14✔
605
                            break;
14✔
606
                        case "ONBUILD":
607
                            problems.push(Validator.createOnbuildChainingDisallowed(onbuild.getInstructionRange().start.line, onbuild.getTriggerRange()));
5✔
608
                            break;
5✔
609
                    }
610
                    break;
153✔
611
                case "SHELL":
612
                    this.checkArguments(instruction, problems, [-1], function (): any {
110✔
613
                        return null;
175✔
614
                    });
615
                    this.checkJSON(document, instruction as JSONInstruction, problems);
110✔
616
                    break;
110✔
617
                case "STOPSIGNAL":
618
                    this.checkArguments(instruction, problems, [1], function (_index: number, argument: string) {
212✔
619
                        if (argument.indexOf("SIG") === 0 || argument.indexOf('$') != -1) {
154✔
620
                            return null;
97✔
621
                        }
622

623
                        for (var i = 0; i < argument.length; i++) {
57✔
624
                            if ('0' > argument.charAt(i) || '9' < argument.charAt(i)) {
57✔
625
                                return Validator.createInvalidStopSignal;
4✔
626
                            }
627
                        }
628
                        return null;
53✔
629
                    });
630
                    let stopsignalArgs = instruction.getExpandedArguments();
212✔
631
                    if (stopsignalArgs.length === 1) {
212✔
632
                        let value = stopsignalArgs[0].getValue();
154✔
633
                        let variables = instruction.getVariables();
154✔
634
                        if (variables.length === 0) {
154✔
635
                            if (value.indexOf('$') !== -1) {
137✔
636
                                const instructionRange = instruction.getInstructionRange();
2✔
637
                                let range = stopsignalArgs[0].getRange();
2✔
638
                                problems.push(Validator.createInvalidStopSignal(instructionRange.start.line, range.start, range.end, value));
2✔
639
                            }
640
                        } else {
641
                            for (let variable of variables) {
17✔
642
                                let variableRange = variable.getRange();
17✔
643
                                let variableDefinition = this.document.getText().substring(
17✔
644
                                    this.document.offsetAt(variableRange.start),
645
                                    this.document.offsetAt(variableRange.end)
646
                                );
647
                                // an un-expanded variable is here
648
                                if (value.includes(variableDefinition) && !variable.isBuildVariable() && !variable.isDefined()) {
17✔
649
                                    const instructionRange = instruction.getInstructionRange();
1✔
650
                                    let range = stopsignalArgs[0].getRange();
1✔
651
                                    problems.push(Validator.createInvalidStopSignal(instructionRange.start.line, range.start, range.end, ""));
1✔
652
                                    break;
1✔
653
                                }
654
                            }
655
                        }
656
                    }
657
                    break;
212✔
658
                case "EXPOSE":
659
                    let exposeArgs = instruction.getArguments();
588✔
660
                    let exposeExpandedArgs = instruction.getExpandedArguments();
588✔
661
                    if (exposeExpandedArgs.length === 0) {
588✔
662
                        let range = instruction.getInstructionRange();
50✔
663
                        problems.push(Validator.createMissingArgument(range.start.line, range.start, range.end));
50✔
664
                    } else {
665
                        const regex = /^([0-9])+(-[0-9]+)?(:([0-9])+(-[0-9]*)?)?(\/(\w*))?(\/\w*)*$/;
538✔
666
                        argCheck: for (let i = 0; i < exposeExpandedArgs.length; i++) {
538✔
667
                            let value = exposeExpandedArgs[i].getValue()
542✔
668
                            if (value.charAt(0) === '"' && value.charAt(value.length - 1) === '"') {
542✔
669
                                value = value.substring(1, value.length - 1);
2✔
670
                            }
671
                            const match = regex.exec(value);
542✔
672
                            if (match) {
542✔
673
                                if (match[7]) {
511✔
674
                                    const protocol = match[7].toLowerCase();
292✔
675
                                    if (protocol !== "" && protocol !== "tcp" && protocol !== "udp" && protocol !== "sctp") {
292✔
676
                                        const range = exposeExpandedArgs[i].getRange();
4✔
677
                                        const rangeStart = this.document.offsetAt(range.start);
4✔
678
                                        const rawArg = this.document.getText().substring(
4✔
679
                                            rangeStart, this.document.offsetAt(range.end)
680
                                        );
681
                                        const start = rangeStart + rawArg.indexOf(match[7].substring(0, 1));
4✔
682
                                        const end = protocol.length === 1 ? rangeStart + start + 1 : rangeStart + rawArg.length;
4✔
683
                                        problems.push(Validator.createInvalidProto(instruction.getInstructionRange().start.line, this.document.positionAt(start), this.document.positionAt(end), match[7]));
4✔
684
                                    }
685
                                }
686
                            } else {
687
                                // see if we're referencing a variable here
688
                                if (value.charAt(0) === '$') {
31✔
689
                                    continue argCheck;
4✔
690
                                }
691
                                problems.push(Validator.createInvalidPort(instruction.getInstructionRange().start.line, exposeExpandedArgs[i].getRange(), value));
27✔
692
                            }
693
                        }
694
                    }
695
                    break;
588✔
696
                case "ADD":
697
                    const add = instruction as Add;
156✔
698
                    const addFlags = add.getFlags();
156✔
699
                    const addInstructionRange = instruction.getInstructionRange();
156✔
700
                    for (let flag of addFlags) {
156✔
701
                        const name = flag.getName();
43✔
702
                        const flagRange = flag.getRange();
43✔
703
                        if (name === "") {
43✔
704
                            problems.push(Validator.createUnknownAddFlag(addInstructionRange.start.line, flagRange.start, flagRange.end, name));
1✔
705
                        } else if (name === "link" || name === "keep-git-dir") {
42✔
706
                            const problem = this.checkFlagBoolean(addInstructionRange.start.line, flag);
14✔
707
                            if (problem !== null) {
14✔
708
                                problems.push(problem);
3✔
709
                            }
710
                        } else if (name !== "chmod" && name !== "chown" && name !== "checksum" && name !== "exclude") {
28✔
711
                            let range = flag.getNameRange();
6✔
712
                            problems.push(Validator.createUnknownAddFlag(addInstructionRange.start.line, flagRange.start, range.end, name));
6✔
713
                        }
714
                    }
715
                    const addDestinationDiagnostic = this.checkDestinationIsDirectory(add, Validator.createADDRequiresAtLeastTwoArguments, Validator.createADDDestinationNotDirectory);
156✔
716
                    if (addDestinationDiagnostic !== null) {
156✔
717
                        problems.push(addDestinationDiagnostic);
69✔
718
                    }
719
                    this.checkFlagValue(addInstructionRange.start.line, addFlags, ["chmod", "chown", "checksum", "exclude"], problems);
156✔
720
                    this.checkDuplicateFlags(addInstructionRange.start.line, addFlags, ["chmod", "chown", "checksum", "keep-git-dir", "link"], problems);
156✔
721
                    this.checkJSONQuotes(instruction, problems);
156✔
722
                    break;
156✔
723
                case "COPY":
724
                    const copyInstructionRange = instruction.getInstructionRange();
175✔
725
                    let copy = instruction as Copy;
175✔
726
                    let flags = copy.getFlags();
175✔
727
                    if (flags.length > 0) {
175✔
728
                        for (let flag of flags) {
44✔
729
                            const name = flag.getName();
53✔
730
                            const flagRange = flag.getRange();
53✔
731
                            if (name === "") {
53✔
732
                                problems.push(Validator.createUnknownCopyFlag(copyInstructionRange.start.line, flagRange.start, flagRange.end, name));
1✔
733
                            } else if (name === "link" || name === "parents") {
52✔
734
                                const problem = this.checkFlagBoolean(copyInstructionRange.start.line, flag);
18✔
735
                                if (problem !== null) {
18✔
736
                                    problems.push(problem);
4✔
737
                                }
738
                            } else if (name !== "chmod" && name !== "chown" && name !== "from" && name !== "exclude" && name !== "parents") {
34✔
739
                                let range = flag.getNameRange();
7✔
740
                                problems.push(Validator.createUnknownCopyFlag(copyInstructionRange.start.line, flagRange.start, range.end, name));
7✔
741
                            }
742
                        }
743

744
                        let flag = copy.getFromFlag();
44✔
745
                        if (flag) {
44✔
746
                            let value = flag.getValue();
7✔
747
                            if (value !== null) {
7✔
748
                                let regexp = new RegExp(/^[a-zA-Z0-9].*$/);
6✔
749
                                if (!regexp.test(value)) {
6✔
750
                                    let range = value === "" ? flag.getRange() : flag.getValueRange();
3✔
751
                                    problems.push(Validator.createFlagInvalidFrom(copyInstructionRange.start.line, range.start, range.end, value));
3✔
752
                                }
753
                            }
754
                        }
755
                    }
756
                    const copyDestinationDiagnostic = this.checkDestinationIsDirectory(copy, Validator.createCOPYRequiresAtLeastTwoArguments, Validator.createCOPYDestinationNotDirectory);
175✔
757
                    if (copyDestinationDiagnostic !== null) {
175✔
758
                        problems.push(copyDestinationDiagnostic);
74✔
759
                    }
760
                    this.checkFlagValue(copyInstructionRange.start.line, flags, ["chmod", "chown", "from", "exclude"], problems);
175✔
761
                    this.checkDuplicateFlags(copyInstructionRange.start.line, flags, ["chmod", "chown", "from", "link", "parents"], problems);
175✔
762
                    this.checkJSONQuotes(instruction, problems);
175✔
763
                    break;
175✔
764
                case "WORKDIR":
765
                    this.checkArguments(instruction, problems, [-1], function (): any {
235✔
766
                        return null;
201✔
767
                    });
768

769
                    let content = instruction.getArgumentsContent();
235✔
770
                    if (content) {
235✔
771
                        // strip out any surrounding quotes
772
                        const first = content.substring(0, 1);
185✔
773
                        const last = content.substring(content.length - 1);
185✔
774
                        if ((first === '\'' && last === '\'') || (first === '"' && last === '"')) {
185✔
775
                            content = content.substring(1, content.length - 1);
24✔
776
                        }
777
                        let regexp = new RegExp(/^(\$|([a-zA-Z](\$|:(\$|\\|\/)))).*$/);
185✔
778
                        if (!content.startsWith('/') && !regexp.test(content)) {
185✔
779
                            let problem = this.createWORKDIRNotAbsolute(instruction.getInstructionRange().start.line, instruction.getArgumentsRange());
32✔
780
                            if (problem) {
32✔
781
                                problems.push(problem);
24✔
782
                            }
783
                        }
784
                    }
785
                    break;
235✔
786
                default:
787
                    this.checkArguments(instruction, problems, [-1], function (): any {
209✔
788
                        return null;
109✔
789
                    });
790
                    break;
209✔
791
            }
792
        }
793
    }
794

795
    private hasHeredocs(args: Argument[]): boolean {
796
        for (const arg of args) {
62✔
797
            if (arg.getValue().startsWith("<<")) {
142✔
798
                return true;
28✔
799
            }
800
        }
801
        return false;
34✔
802
    }
803

804
    private getDestinationArgument(args: Argument[]): Argument {
805
        if (this.hasHeredocs(args)) {
33✔
806
            const initialLine = args[0].getRange().start.line;
16✔
807
            let candidate: Argument | null = null;
16✔
808
            for (let i = 1; i < args.length; i++) {
16✔
809
                if (args[i].getRange().start.line === initialLine) {
44✔
810
                    candidate = args[i];
28✔
811
                } else {
812
                    // stop searching once we're on another line
813
                    break;
16✔
814
                }
815
            }
816
            return candidate;
16✔
817
        }
818
        return args[args.length - 1];
17✔
819
    }
820

821
    private checkDestinationIsDirectory(instruction: JSONInstruction, requiresTwoArgumentsFunction: (instructionLine: uinteger, range: Range) => Diagnostic, notDirectoryFunction: (instructionLine: uinteger, range: Range) => Diagnostic): Diagnostic | null {
822
        if (instruction.getClosingBracket()) {
331✔
823
            return this.checkJsonDestinationIsDirectory(instruction, requiresTwoArgumentsFunction, notDirectoryFunction);
59✔
824
        }
825

826
        const args = instruction.getArguments();
272✔
827
        if (args.length === 1) {
272✔
828
            return requiresTwoArgumentsFunction(instruction.getInstructionRange().start.line, args[0].getRange());
4✔
829
        } else if (args.length === 0) {
268✔
830
            const instructionRange = instruction.getInstructionRange();
104✔
831
            return requiresTwoArgumentsFunction(instructionRange.start.line, instructionRange);
104✔
832
        } else if (args.length > 2) {
164✔
833
            const lastArg = this.getDestinationArgument(args);
33✔
834
            if (lastArg === null) {
33✔
835
                const instructionRange = instruction.getInstructionRange();
4✔
836
                return requiresTwoArgumentsFunction(instructionRange.start.line, instructionRange);
4✔
837
            } else if (this.hasHeredocs(args)) {
29✔
838
                return null;
12✔
839
            }
840
            const variables = instruction.getVariables();
17✔
841
            if (variables.length !== 0) {
17✔
842
                const lastJsonStringOffset = this.document.offsetAt(lastArg.getRange().end);
4✔
843
                const lastVarOffset = this.document.offsetAt(variables[variables.length - 1].getRange().end);
4✔
844
                if (lastJsonStringOffset === lastVarOffset || lastJsonStringOffset -1 === lastVarOffset) {
4!
845
                    return null;
4✔
846
                }
847
            }
848
            const destination = lastArg.getValue();
13✔
849
            const lastChar = destination.charAt(destination.length - 1);
13✔
850
            if (lastChar !== '\\' && lastChar !== '/') {
13✔
851
                return notDirectoryFunction(instruction.getInstructionRange().start.line, lastArg.getRange());
8✔
852
            }
853
        }
854
        return null;
136✔
855
    }
856

857
    private createDuplicatesDiagnostics(problems: Diagnostic[], severity: ValidationSeverity, instruction: string, instructions: Instruction[]): void {
858
        if (instructions.length > 1) {
34,653✔
859
            // decrement length by 1 because we want to ignore the last one
860
            for (let i = 0; i < instructions.length - 1; i++) {
40✔
861
                const instructionRange = instructions[i].getInstructionRange();
40✔
862
                const diagnostic = this.createMultipleInstructions(instructionRange.start.line, instructionRange, severity, instruction);
40✔
863
                if (diagnostic) {
40✔
864
                    problems.push(diagnostic);
36✔
865
                }
866
            }
867
        }
868
    }
869

870
    private createDuplicateBuildStageNameDiagnostics(problems: Diagnostic[], froms: From[]): void {
871
        const names: any = {};
5,730✔
872
        for (let from of froms) {
5,730✔
873
            let name = from.getBuildStage();
5,821✔
874
            if (name !== null) {
5,821✔
875
                name = name.toLowerCase();
36✔
876
                if (names[name] === undefined) {
36✔
877
                    names[name] = [from];
32✔
878
                } else {
879
                    names[name].push(from);
4✔
880
                }
881
            }
882
        }
883

884
        for (const name in names) {
5,730✔
885
            // duplicates found
886
            if (names[name].length > 1) {
32✔
887
                for (const from of names[name]) {
4✔
888
                    problems.push(Validator.createDuplicateBuildStageName(from.getInstructionRange().start.line, from.getBuildStageRange(), name));
8✔
889
                }
890
            }
891
        }
892
    }
893

894
    private checkJsonDestinationIsDirectory(instruction: JSONInstruction, requiresTwoArgumentsFunction: (instructionLine: uinteger, range: Range) => Diagnostic, notDirectoryFunction: (instructionLine: uinteger, range: Range) => Diagnostic): Diagnostic | null {
895
        const jsonStrings = instruction.getJSONStrings();
59✔
896
        if (jsonStrings.length === 0) {
59✔
897
            return requiresTwoArgumentsFunction(instruction.getInstructionRange().start.line, instruction.getArgumentsRange());
4✔
898
        } else if (jsonStrings.length === 1) {
55✔
899
            return requiresTwoArgumentsFunction(instruction.getInstructionRange().start.line, jsonStrings[0].getJSONRange());
6✔
900
        } else if (jsonStrings.length > 2) {
49✔
901
            const lastJsonString = jsonStrings[jsonStrings.length - 1];
37✔
902
            const variables = instruction.getVariables();
37✔
903
            if (variables.length !== 0) {
37✔
904
                const lastVar = variables[variables.length - 1];
12✔
905
                const lastJsonStringOffset = this.document.offsetAt(lastJsonString.getRange().end);
12✔
906
                const lastVarOffset = this.document.offsetAt(lastVar.getRange().end);
12✔
907
                if (lastJsonStringOffset === lastVarOffset || lastJsonStringOffset -1 === lastVarOffset) {
12✔
908
                    return null;
12✔
909
                }
910
            }
911
            const destination = lastJsonString.getValue();
25✔
912
            const lastChar = destination.charAt(destination.length - 2);
25✔
913
            if (lastChar !== '\\' && lastChar !== '/') {
25✔
914
                return notDirectoryFunction(instruction.getInstructionRange().start.line, jsonStrings[jsonStrings.length - 1].getJSONRange());
13✔
915
            }
916
        }
917
        return null;
24✔
918
    }
919

920
    private checkFlagValue(instructionLine: uinteger, flags: Flag[], validFlagNames: string[], problems: Diagnostic[]): void {
921
        for (let flag of flags) {
6,396✔
922
            let flagName = flag.getName();
238✔
923
            // only validate flags with the right name
924
            if (flag.getValue() === null && validFlagNames.indexOf(flagName) !== -1) {
238✔
925
                problems.push(Validator.createFlagMissingValue(instructionLine, flag.getNameRange(), flagName));
14✔
926
            }
927
        }
928
    }
929

930
    /**
931
     * Checks that the given boolean flag is valid. A boolean flag should
932
     * either have no value defined (--flag) or the value should
933
     * case-insensitively be either "true" or "false" (--flag==tRUe is
934
     * valid).
935
     */
936
    private checkFlagBoolean(instructionLine: uinteger, flag: Flag): Diagnostic | null {
937
        const flagValue = flag.getValue();
32✔
938
        if (flagValue === "") {
32✔
939
            return Validator.createFlagMissingValue(instructionLine, flag.getNameRange(), flag.getName());
3✔
940
        } else if (flagValue !== null) {
29✔
941
            const convertedFlagValue = flagValue.toLowerCase();
25✔
942
            if (convertedFlagValue !== "true" && convertedFlagValue !== "false") {
25✔
943
                return Validator.createFlagExpectedBooleanValue(instructionLine, flag.getValueRange(), flag.getName(), flagValue);
4✔
944
            }
945
        }
946
        return null;
25✔
947
    }
948

949
    private checkFlagDuration(instructionLine: uinteger, flags: Flag[], validFlagNames: string[], problems: Diagnostic[]): void {
950
        flagCheck: for (let flag of flags) {
239✔
951
            let flagName = flag.getName();
136✔
952
            // only validate flags with the right name
953
            if (validFlagNames.indexOf(flagName) !== -1) {
136✔
954
                let value = flag.getValue();
113✔
955
                if (value !== null && value.length !== 0) {
113✔
956
                    switch (value.charAt(0)) {
105✔
957
                        case '0':
735✔
958
                        case '1':
959
                        case '2':
960
                        case '3':
961
                        case '4':
962
                        case '5':
963
                        case '6':
964
                        case '7':
965
                        case '8':
966
                        case '9':
967
                        case '.':
968
                        case '-':
969
                            break;
99✔
970
                        default:
971
                            let range = flag.getValueRange();
6✔
972
                            problems.push(Validator.createFlagInvalidDuration(instructionLine, range.start, range.end, value));
6✔
973
                            continue flagCheck;
6✔
974
                    }
975

976
                    let durationSpecified = false;
99✔
977
                    let start = 0;
99✔
978
                    let duration = 0;
99✔
979
                    let digitFound = false;
99✔
980
                    let hyphenFound = false;
99✔
981
                    let periodsDetected = 0;
99✔
982
                    durationParse: for (let i = 0; i < value.length; i++) {
99✔
983
                        durationSpecified = false;
374✔
984
                        switch (value.charAt(i)) {
374✔
985
                            case '-':
1,919✔
986
                                if (digitFound) {
24✔
987
                                    let range = flag.getValueRange();
3✔
988
                                    problems.push(Validator.createFlagUnknownUnit(instructionLine, range, value.charAt(i), value));
3✔
989
                                    continue flagCheck;
3✔
990
                                } else if (hyphenFound) {
21✔
991
                                    let range = flag.getValueRange();
3✔
992
                                    problems.push(Validator.createFlagInvalidDuration(instructionLine, range.start, range.end, value));
3✔
993
                                    continue flagCheck;
3✔
994
                                }
995
                                hyphenFound = true;
18✔
996
                                continue;
18✔
997
                            case '.':
998
                                periodsDetected++;
28✔
999
                                continue;
28✔
1000
                            case '0':
1001
                            case '1':
1002
                            case '2':
1003
                            case '3':
1004
                            case '4':
1005
                            case '5':
1006
                            case '6':
1007
                            case '7':
1008
                            case '8':
1009
                            case '9':
1010
                                digitFound = true;
214✔
1011
                                continue;
214✔
1012
                            default:
1013
                                if (periodsDetected > 1) {
108✔
1014
                                    let range = flag.getValueRange();
9✔
1015
                                    problems.push(Validator.createFlagMissingDuration(instructionLine, range.start, range.end, value));
9✔
1016
                                    continue flagCheck;
9✔
1017
                                }
1018
                                periodsDetected = 0;
99✔
1019
                                let time = parseFloat(value.substring(start, i));
99✔
1020
                                for (let j = i + 1; j < value.length; j++) {
99✔
1021
                                    if (Validator.isNumberRelated(value.charAt(j))) {
70✔
1022
                                        let unit = value.substring(i, j);
36✔
1023
                                        if (time < 0 || (value.charAt(start) === '-' && time === 0)) {
36✔
1024
                                            let nameRange = flag.getNameRange();
6✔
1025
                                            problems.push(Validator.createFlagLessThan1ms(instructionLine, nameRange.start, nameRange.end, flagName));
6✔
1026
                                            continue flagCheck;
6✔
1027
                                        }
1028
                                        switch (unit) {
30✔
1029
                                            case 'h':
33✔
1030
                                                // hours
1031
                                                duration += time * 1000 * 60 * 60;
1✔
1032
                                                i = j - 1;
1✔
1033
                                                start = i;
1✔
1034
                                                durationSpecified = true;
1✔
1035
                                                continue durationParse;
1✔
1036
                                            case 'm':
1037
                                                // minutes
1038
                                                duration += time * 1000 * 60;
1✔
1039
                                                i = j - 1;
1✔
1040
                                                start = i;
1✔
1041
                                                durationSpecified = true;
1✔
1042
                                                continue durationParse;
1✔
1043
                                            case 's':
1044
                                                // seconds
1045
                                                duration += time * 1000;
15✔
1046
                                                i = j - 1;
15✔
1047
                                                start = i;
15✔
1048
                                                durationSpecified = true;
15✔
1049
                                                continue durationParse;
15✔
1050
                                            case "ms":
1051
                                                // milliseconds
1052
                                                duration += time;
1✔
1053
                                                i = j - 1;
1✔
1054
                                                start = i;
1✔
1055
                                                durationSpecified = true;
1✔
1056
                                                continue durationParse;
1✔
1057
                                            case "us":
1058
                                            case "µs":
1059
                                                // microseconds
1060
                                                duration += time / 1000;
3✔
1061
                                                i = j - 1;
3✔
1062
                                                start = i;
3✔
1063
                                                durationSpecified = true;
3✔
1064
                                                continue durationParse;
3✔
1065
                                            case "ns":
1066
                                                // nanoseconds
1067
                                                duration += time / 1000000;
3✔
1068
                                                i = j - 1;
3✔
1069
                                                start = i;
3✔
1070
                                                durationSpecified = true;
3✔
1071
                                                continue durationParse;
3✔
1072
                                            default:
1073
                                                let range = flag.getValueRange();
6✔
1074
                                                problems.push(Validator.createFlagUnknownUnit(instructionLine, range, unit, value));
6✔
1075
                                                continue flagCheck;
6✔
1076
                                        }
1077
                                    }
1078
                                }
1079
                                if (time < 0 || (value.charAt(start) === '-' && time === 0)) {
63✔
1080
                                    let nameRange = flag.getNameRange();
6✔
1081
                                    problems.push(Validator.createFlagLessThan1ms(instructionLine, nameRange.start, nameRange.end, flagName));
6✔
1082
                                    continue flagCheck;
6✔
1083
                                }
1084
                                let unit = value.substring(i, value.length);
57✔
1085
                                switch (unit) {
57✔
1086
                                    case 'h':
63✔
1087
                                        // hours
1088
                                        duration += time * 1000 * 60 * 60;
3✔
1089
                                        durationSpecified = true;
3✔
1090
                                        break durationParse;
3✔
1091
                                    case 'm':
1092
                                        // minutes
1093
                                        duration += time * 1000 * 60;
3✔
1094
                                        durationSpecified = true;
3✔
1095
                                        break durationParse;
3✔
1096
                                    case 's':
1097
                                        // seconds
1098
                                        duration += time * 1000;
24✔
1099
                                        durationSpecified = true;
24✔
1100
                                        break durationParse;
24✔
1101
                                    case "ms":
1102
                                        // minutes
1103
                                        duration += time;
9✔
1104
                                        durationSpecified = true;
9✔
1105
                                        break durationParse;
9✔
1106
                                    case "us":
1107
                                    case "µs":
1108
                                        // microseconds
1109
                                        duration += time / 1000;
9✔
1110
                                        durationSpecified = true;
9✔
1111
                                        break durationParse;
9✔
1112
                                    case "ns":
1113
                                        // nanoseconds
1114
                                        duration += time / 1000000;
3✔
1115
                                        durationSpecified = true;
3✔
1116
                                        break durationParse;
3✔
1117
                                    default:
1118
                                        let range = flag.getValueRange();
6✔
1119
                                        problems.push(Validator.createFlagUnknownUnit(instructionLine, range, unit, value));
6✔
1120
                                        break;
6✔
1121
                                }
1122
                                continue flagCheck;
6✔
1123
                        }
1124
                    }
1125

1126
                    if (!durationSpecified) {
60✔
1127
                        let range = flag.getValueRange();
9✔
1128
                        problems.push(Validator.createFlagMissingDuration(instructionLine, range.start, range.end, value));
9✔
1129
                    } else if (duration < 1) {
51✔
1130
                        let range = flag.getNameRange();
15✔
1131
                        problems.push(Validator.createFlagLessThan1ms(instructionLine, range.start, range.end, flagName));
15✔
1132
                    }
1133
                }
1134
            }
1135
        }
1136
    }
1137

1138
    private static isNumberRelated(character: string) {
1139
        switch (character) {
70✔
1140
            case '.':
270✔
1141
            case '0':
1142
            case '1':
1143
            case '2':
1144
            case '3':
1145
            case '4':
1146
            case '5':
1147
            case '6':
1148
            case '7':
1149
            case '8':
1150
            case '9':
1151
                return true;
36✔
1152
        }
1153
        return false;
34✔
1154
    }
1155

1156
    private checkDuplicateFlags(instructionLine: uinteger, flags: Flag[], validFlags: string[], problems: Diagnostic[]): void {
1157
        let flagNames = flags.map(function (flag) {
570✔
1158
            return flag.getName();
232✔
1159
        });
1160
        for (let validFlag of validFlags) {
570✔
1161
            let index = flagNames.indexOf(validFlag);
2,850✔
1162
            let lastIndex = flagNames.lastIndexOf(validFlag);
2,850✔
1163
            if (index !== lastIndex) {
2,850✔
1164
                let range = flags[index].getNameRange();
11✔
1165
                problems.push(Validator.createFlagDuplicate(instructionLine, range.start, range.end, flagNames[index]));
11✔
1166
                range = flags[lastIndex].getNameRange();
11✔
1167
                problems.push(Validator.createFlagDuplicate(instructionLine, range.start, range.end, flagNames[index]));
11✔
1168
            }
1169
        }
1170
    }
1171

1172
    private checkJSON(document: TextDocument, instruction: JSONInstruction, problems: Diagnostic[]) {
1173
        let argsContent = instruction.getArgumentsContent();
110✔
1174
        if (argsContent === null) {
110✔
1175
            return;
50✔
1176
        }
1177

1178
        let argsRange = instruction.getArgumentsRange();
60✔
1179
        let args = instruction.getArguments();
60✔
1180
        if ((args.length === 1 && args[0].getValue() === "[]") ||
60✔
1181
            (args.length === 2 && args[0].getValue() === '[' && args[1].getValue() === ']')) {
1182
            problems.push(Validator.createShellRequiresOne(instruction.getInstructionRange().start.line, argsRange));
3✔
1183
            return;
3✔
1184
        }
1185

1186
        const closing = instruction.getClosingBracket();
57✔
1187
        if (closing) {
57✔
1188
            let content = document.getText();
45✔
1189
            content = content.substring(
45✔
1190
                document.offsetAt(instruction.getOpeningBracket().getRange().end),
1191
                document.offsetAt(closing.getRange().start)
1192
            );
1193
            content = content.trim();
45✔
1194
            if (content.charAt(content.length - 1) !== '"') {
45✔
1195
                problems.push(Validator.createShellJsonForm(instruction.getInstructionRange().start.line, argsRange));
1✔
1196
            }
1197
        } else {
1198
            problems.push(Validator.createShellJsonForm(instruction.getInstructionRange().start.line, argsRange));
12✔
1199
        }
1200
    }
1201

1202
    private checkJSONQuotes(instruction: Instruction, problems: Diagnostic[]) {
1203
        let argsContent = instruction.getArgumentsContent();
784✔
1204
        if (argsContent === null) {
784✔
1205
            return;
308✔
1206
        }
1207

1208
        let argsRange = instruction.getArgumentsRange();
476✔
1209
        let args = instruction.getArguments();
476✔
1210
        if ((args.length === 1 && args[0].getValue() === "[]") ||
476✔
1211
            (args.length === 2 && args[0].getValue() === '[' && args[1].getValue() === ']')) {
1212
            return;
4✔
1213
        }
1214

1215
        let jsonLike = false;
472✔
1216
        let last: string = null;
472✔
1217
        let quoted = false;
472✔
1218
        argsCheck: for (let i = 0; i < argsContent.length; i++) {
472✔
1219
            switch (argsContent.charAt(i)) {
1,377✔
1220
                case '[':
1,504✔
1221
                    if (last === null) {
147✔
1222
                        last = '[';
145✔
1223
                        jsonLike = true;
145✔
1224
                    }
1225
                    break;
147✔
1226
                case '\'':
1227
                    if (last === '[' || last === ',') {
151✔
1228
                        quoted = true;
77✔
1229
                        last = '\'';
77✔
1230
                        continue;
77✔
1231
                    } else if (last === '\'') {
74✔
1232
                        if (quoted) {
73✔
1233
                            // quoted string done
1234
                            quoted = false;
72✔
1235
                        } else {
1236
                            // should be a , or a ]
1237
                            break argsCheck;
1✔
1238
                        }
1239
                    } else {
1240
                        break argsCheck;
1✔
1241
                    }
1242
                    break;
72✔
1243
                case ',':
1244
                    if (!jsonLike) {
54✔
1245
                        break argsCheck;
1✔
1246
                    } else if (!quoted) {
53✔
1247
                        if (last === '\'') {
46✔
1248
                            last = ','
45✔
1249
                        } else {
1250
                            break argsCheck;
1✔
1251
                        }
1252
                    }
1253
                    break;
52✔
1254
                case ']':
1255
                    if (!quoted) {
49✔
1256
                        if (last === '\'' || last === ',') {
37✔
1257
                            last = null;
36✔
1258
                            const problem = Validator.createJSONInSingleQuotes(instruction.getInstructionRange().start.line, argsRange, this.settings.instructionJSONInSingleQuotes);
36✔
1259
                            if (problem) {
36✔
1260
                                problems.push(problem);
30✔
1261
                            }
1262
                        }
1263
                        break argsCheck;
37✔
1264
                    }
1265
                    break;
12✔
1266
                case ' ':
1267
                case '\t':
1268
                    break;
127✔
1269
                default:
1270
                    if (!quoted) {
849✔
1271
                        break argsCheck;
423✔
1272
                    }
1273
                    break;
426✔
1274
            }
1275
        }
1276
    }
1277

1278
    private static dockerProblems = {
1✔
1279
        "baseNameEmpty": "base name (${0}) should not be blank",
1280

1281
        "directiveCasing": "Parser directives should be written in lowercase letters",
1282
        "directiveEscapeDuplicated": "only one escape parser directive can be used",
1283
        "directiveEscapeInvalid": "invalid ESCAPE '${0}'. Must be ` or \\",
1284

1285
        "noSourceImage": "No source image provided with `FROM`",
1286

1287
        "emptyContinuationLine": "Empty continuation line",
1288

1289
        "fromRequiresOneOrThreeArguments": "FROM requires either one or three arguments",
1290

1291
        "invalidAs": "Second argument should be AS",
1292
        "invalidPort": "Invalid containerPort: ${0}",
1293
        "invalidProtocol": "Invalid proto: ${0}",
1294
        "invalidReferenceFormat": "invalid reference format",
1295
        "invalidStopSignal": "Invalid signal: ${0}",
1296
        "invalidSyntax": "parsing \"${0}\": invalid syntax",
1297
        "invalidDestination": "When using ${0} with more than one source file, the destination must be a directory and end with a / or a \\",
1298

1299
        "syntaxMissingEquals": "Syntax error - can't find = in \"${0}\". Must be of the form: name=value",
1300
        "syntaxMissingNames": "${0} names cannot be blank",
1301
        "syntaxMissingSingleQuote": "failed to process \"${0}\": unexpected end of statement while looking for matching single-quote",
1302
        "syntaxMissingDoubleQuote": "failed to process \"${0}\": unexpected end of statement while looking for matching double-quote",
1303

1304
        "duplicateBuildStageName": "duplicate name ${0}",
1305
        "invalidBuildStageName": "invalid name for build stage: \"${0}\", name can't start with a number or contain symbols",
1306

1307
        "flagAtLeastOne": "${0} must be at least 1 (not ${1})",
1308
        "flagDuplicate": "Duplicate flag specified: ${0}",
1309
        "flagInvalidDuration": "time: invalid duration ${0}",
1310
        "flagInvalidFrom": "invalid from flag value ${0}: invalid reference format",
1311
        "flagExpectedBooleanValue": "expecting boolean value for flag ${0}, not: ${1}",
1312
        "flagLessThan1ms": "Interval \"${0}\" cannot be less than 1ms",
1313
        "flagMissingDuration": "time: missing unit in duration ${0}",
1314
        "flagMissingValue": "Missing a value on flag: ${0}",
1315
        "flagUnknown": "Unknown flag: ${0}",
1316
        "flagUnknownUnit": "time: unknown unit ${0} in duration ${1}",
1317

1318
        "instructionExtraArgument": "Instruction has an extra argument",
1319
        "instructionMissingArgument": "Instruction has no arguments",
1320
        "instructionMultiple": "There can only be one ${0} instruction in a Dockerfile or build stage. Only the last one will have an effect.",
1321
        "instructionRequiresOneArgument": "${0} requires exactly one argument",
1322
        "instructionRequiresAtLeastOneArgument": "${0} requires at least one argument",
1323
        "instructionRequiresAtLeastTwoArguments": "${0} requires at least two arguments",
1324
        "instructionRequiresTwoArguments": "${0} must have two arguments",
1325
        "instructionUnnecessaryArgument": "${0} takes no arguments",
1326
        "instructionUnknown": "Unknown instruction: ${0}",
1327
        "instructionCasing": "Instructions should be written in uppercase letters",
1328
        "instructionJSONInSingleQuotes": "Instruction written as a JSON array but is using single quotes instead of double quotes",
1329

1330
        "variableModifierUnsupported": "failed to process \"${0}\": unsupported modifier (${1}) in substitution",
1331

1332
        "onbuildChainingDisallowed": "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed",
1333
        "onbuildTriggerDisallowed": "${0} isn't allowed as an ONBUILD trigger",
1334

1335
        "shellJsonForm": "SHELL requires the arguments to be in JSON form",
1336
        "shellRequiresOne": "SHELL requires at least one argument",
1337

1338
        "deprecatedMaintainer": "MAINTAINER has been deprecated",
1339

1340
        "healthcheckCmdArgumentMissing": "Missing command after HEALTHCHECK CMD",
1341
        "healthcheckTypeUnknown": "Unknown type\"${0}\" in HEALTHCHECK (try CMD)",
1342

1343
        "workdirPathNotAbsolute": "WORKDIR paths should be absolute"
1344
    };
1345

1346
    private static formatMessage(text: string, ...variables: string[]): string {
1347
        for (let i = 0; i < variables.length; i++) {
1,207✔
1348
            text = text.replace("${" + i + "}", variables[i]);
1,291✔
1349
        }
1350
        return text;
1,207✔
1351
    }
1352

1353
    public static getDiagnosticMessage_DirectiveCasing() {
1354
        return Validator.dockerProblems["directiveCasing"];
10✔
1355
    }
1356

1357
    public static getDiagnosticMessage_DirectiveEscapeDuplicated() {
1358
        return Validator.dockerProblems["directiveEscapeDuplicated"];
2✔
1359
    }
1360

1361
    public static getDiagnosticMessage_DirectiveEscapeInvalid(value: string) {
1362
        return Validator.formatMessage(Validator.dockerProblems["directiveEscapeInvalid"], value);
8✔
1363
    }
1364

1365
    public static getDiagnosticMessage_NoSourceImage() {
1366
        return Validator.dockerProblems["noSourceImage"];
34✔
1367
    }
1368

1369
    public static getDiagnosticMessage_EmptyContinuationLine() {
1370
        return Validator.dockerProblems["emptyContinuationLine"];
26✔
1371
    }
1372

1373
    public static getDiagnosticMessage_DuplicateBuildStageName(name: string) {
1374
        return Validator.formatMessage(Validator.dockerProblems["duplicateBuildStageName"], name);
16✔
1375
    }
1376

1377
    public static getDiagnosticMessage_InvalidBuildStageName(name: string) {
1378
        return Validator.formatMessage(Validator.dockerProblems["invalidBuildStageName"], name);
6✔
1379
    }
1380

1381
    public static getDiagnosticMessage_FlagAtLeastOne(flagName: string, flagValue: string) {
1382
        return Validator.formatMessage(Validator.dockerProblems["flagAtLeastOne"], flagName, flagValue);
4✔
1383
    }
1384

1385
    public static getDiagnosticMessage_FlagDuplicate(flag: string) {
1386
        return Validator.formatMessage(Validator.dockerProblems["flagDuplicate"], flag);
44✔
1387
    }
1388

1389
    public static getDiagnosticMessage_FlagInvalidDuration(flag: string) {
1390
        return Validator.formatMessage(Validator.dockerProblems["flagInvalidDuration"], flag);
18✔
1391
    }
1392

1393
    public static getDiagnosticMessage_FlagLessThan1ms(flag: string) {
1394
        return Validator.formatMessage(Validator.dockerProblems["flagLessThan1ms"], flag);
54✔
1395
    }
1396

1397
    public static getDiagnosticMessage_FlagMissingDuration(duration: string) {
1398
        return Validator.formatMessage(Validator.dockerProblems["flagMissingDuration"], duration);
36✔
1399
    }
1400

1401
    public static getDiagnosticMessage_FlagInvalidFromValue(value: string): string {
1402
        return Validator.formatMessage(Validator.dockerProblems["flagInvalidFrom"], value);
6✔
1403
    }
1404

1405
    public static getDiagnosticMessage_FlagExpectedBooleanValue(flag: string, value: string): string {
1406
        return Validator.formatMessage(Validator.dockerProblems["flagExpectedBooleanValue"], flag, value);
8✔
1407
    }
1408

1409
    public static getDiagnosticMessage_FlagMissingValue(flag: string) {
1410
        return Validator.formatMessage(Validator.dockerProblems["flagMissingValue"], flag);
34✔
1411
    }
1412

1413
    public static getDiagnosticMessage_FlagUnknown(flag: string) {
1414
        return Validator.formatMessage(Validator.dockerProblems["flagUnknown"], flag);
62✔
1415
    }
1416

1417
    public static getDiagnosticMessage_FlagUnknownUnit(unit: string, duration: string) {
1418
        return Validator.formatMessage(Validator.dockerProblems["flagUnknownUnit"], unit, duration);
30✔
1419
    }
1420

1421
    public static getDiagnosticMessage_BaseNameEmpty(name: string) {
1422
        return Validator.formatMessage(Validator.dockerProblems["baseNameEmpty"], name);
6✔
1423
    }
1424

1425
    public static getDiagnosticMessage_InvalidAs() {
1426
        return Validator.dockerProblems["invalidAs"];
2✔
1427
    }
1428

1429
    public static getDiagnosticMessage_InvalidPort(port: string) {
1430
        return Validator.formatMessage(Validator.dockerProblems["invalidPort"], port);
54✔
1431
    }
1432

1433
    public static getDiagnosticMessage_InvalidProto(protocol: string) {
1434
        return Validator.formatMessage(Validator.dockerProblems["invalidProtocol"], protocol);
8✔
1435
    }
1436

1437
    public static getDiagnosticMessage_InvalidReferenceFormat() {
1438
        return Validator.dockerProblems["invalidReferenceFormat"];
84✔
1439
    }
1440

1441
    public static getDiagnosticMessage_InvalidSignal(signal: string) {
1442
        return Validator.formatMessage(Validator.dockerProblems["invalidStopSignal"], signal);
14✔
1443
    }
1444

1445
    public static getDiagnosticMessage_InvalidSyntax(syntax: string) {
1446
        return Validator.formatMessage(Validator.dockerProblems["invalidSyntax"], syntax);
4✔
1447
    }
1448

1449
    public static getDiagnosticMessage_InstructionExtraArgument() {
1450
        return Validator.dockerProblems["instructionExtraArgument"];
16✔
1451
    }
1452

1453
    public static getDiagnosticMessage_InstructionMissingArgument() {
1454
        return Validator.dockerProblems["instructionMissingArgument"];
1,304✔
1455
    }
1456

1457
    public static getDiagnosticMessage_ADDDestinationNotDirectory() {
1458
        return Validator.formatMessage(Validator.dockerProblems["invalidDestination"], "ADD");
16✔
1459
    }
1460

1461
    public static getDiagnosticMessage_ADDRequiresAtLeastTwoArguments() {
1462
        return Validator.formatMessage(Validator.dockerProblems["instructionRequiresAtLeastTwoArguments"], "ADD");
122✔
1463
    }
1464

1465
    public static getDiagnosticMessage_COPYDestinationNotDirectory() {
1466
        return Validator.formatMessage(Validator.dockerProblems["invalidDestination"], "COPY");
26✔
1467
    }
1468

1469
    public static getDiagnosticMessage_COPYRequiresAtLeastTwoArguments() {
1470
        return Validator.formatMessage(Validator.dockerProblems["instructionRequiresAtLeastTwoArguments"], "COPY");
122✔
1471
    }
1472

1473
    public static getDiagnosticMessage_HEALTHCHECKRequiresAtLeastOneArgument() {
1474
        return Validator.formatMessage(Validator.dockerProblems["instructionRequiresAtLeastOneArgument"], "HEALTHCHECK");
106✔
1475
    }
1476

1477
    public static getDiagnosticMessage_ENVRequiresTwoArguments() {
1478
        return Validator.formatMessage(Validator.dockerProblems["instructionRequiresTwoArguments"], "ENV");
4✔
1479
    }
1480

1481
    public static getDiagnosticMessage_InstructionRequiresOneOrThreeArguments() {
1482
        return Validator.dockerProblems["fromRequiresOneOrThreeArguments"];
28✔
1483
    }
1484

1485
    public static getDiagnosticMessage_HealthcheckNoneUnnecessaryArgument() {
1486
        return Validator.formatMessage(Validator.dockerProblems["instructionUnnecessaryArgument"], "HEALTHCHECK NONE");
4✔
1487
    }
1488

1489
    public static getDiagnosticMessage_InstructionMultiple(instruction: string) {
1490
        return Validator.formatMessage(Validator.dockerProblems["instructionMultiple"], instruction);
72✔
1491
    }
1492

1493
    public static getDiagnosticMessage_InstructionUnknown(instruction: string) {
1494
        return Validator.formatMessage(Validator.dockerProblems["instructionUnknown"], instruction);
41✔
1495
    }
1496

1497
    public static getDiagnosticMessage_SyntaxMissingEquals(argument: string) {
1498
        return Validator.formatMessage(Validator.dockerProblems["syntaxMissingEquals"], argument);
8✔
1499
    }
1500

1501
    public static getDiagnosticMessage_SyntaxMissingNames(instruction: string) {
1502
        return Validator.formatMessage(Validator.dockerProblems["syntaxMissingNames"], instruction);
20✔
1503
    }
1504

1505
    public static getDiagnosticMessage_SyntaxMissingSingleQuote(key: string) {
1506
        return Validator.formatMessage(Validator.dockerProblems["syntaxMissingSingleQuote"], key);
24✔
1507
    }
1508

1509
    public static getDiagnosticMessage_SyntaxMissingDoubleQuote(key: string) {
1510
        return Validator.formatMessage(Validator.dockerProblems["syntaxMissingDoubleQuote"], key);
54✔
1511
    }
1512

1513
    public static getDiagnosticMessage_InstructionCasing() {
1514
        return Validator.dockerProblems["instructionCasing"];
160✔
1515
    }
1516

1517
    public static getDiagnosticMessage_InstructionJSONInSingleQuotes() {
1518
        return Validator.dockerProblems["instructionJSONInSingleQuotes"];
60✔
1519
    }
1520

1521
    public static getDiagnosticMessage_OnbuildChainingDisallowed() {
1522
        return Validator.dockerProblems["onbuildChainingDisallowed"];
10✔
1523
    }
1524

1525
    public static getDiagnosticMessage_OnbuildTriggerDisallowed(trigger: string) {
1526
        return Validator.formatMessage(Validator.dockerProblems["onbuildTriggerDisallowed"], trigger);
28✔
1527
    }
1528

1529
    public static getDiagnosticMessage_VariableModifierUnsupported(variable: string, modifier: string) {
1530
        return Validator.formatMessage(Validator.dockerProblems["variableModifierUnsupported"], variable, modifier);
90✔
1531
    }
1532

1533
    public static getDiagnosticMessage_ShellJsonForm() {
1534
        return Validator.dockerProblems["shellJsonForm"];
26✔
1535
    }
1536

1537
    public static getDiagnosticMessage_ShellRequiresOne() {
1538
        return Validator.dockerProblems["shellRequiresOne"];
6✔
1539
    }
1540

1541
    public static getDiagnosticMessage_DeprecatedMaintainer() {
1542
        return Validator.dockerProblems["deprecatedMaintainer"];
12✔
1543
    }
1544

1545
    public static getDiagnosticMessage_HealthcheckCmdArgumentMissing() {
1546
        return Validator.dockerProblems["healthcheckCmdArgumentMissing"];
6✔
1547
    }
1548

1549
    public static getDiagnosticMessage_HealthcheckTypeUnknown(type: string) {
1550
        return Validator.formatMessage(Validator.dockerProblems["healthcheckTypeUnknown"], type);
10✔
1551
    }
1552

1553
    public static getDiagnosticMessage_WORKDIRPathNotAbsolute() {
1554
        return Validator.formatMessage(Validator.dockerProblems["workdirPathNotAbsolute"]);
48✔
1555
    }
1556

1557
    private static createDuplicatedEscapeDirective(start: Position, end: Position): Diagnostic {
1558
        return Validator.createError(null, start, end, Validator.getDiagnosticMessage_DirectiveEscapeDuplicated(), ValidationCode.DUPLICATED_ESCAPE_DIRECTIVE, [DiagnosticTag.Unnecessary]);
1✔
1559
    }
1560

1561
    private static createInvalidEscapeDirective(start: Position, end: Position, value: string): Diagnostic {
1562
        return Validator.createError(null, start, end, Validator.getDiagnosticMessage_DirectiveEscapeInvalid(value), ValidationCode.INVALID_ESCAPE_DIRECTIVE);
4✔
1563
    }
1564

1565
    private static createDuplicateBuildStageName(instructionLine: uinteger, range: Range, name: string): Diagnostic {
1566
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_DuplicateBuildStageName(name), ValidationCode.DUPLICATE_BUILD_STAGE_NAME);
8✔
1567
    }
1568

1569
    private static createInvalidBuildStageName(instructionLine: uinteger | null, range: Range, name: string): Diagnostic {
1570
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InvalidBuildStageName(name), ValidationCode.INVALID_BUILD_STAGE_NAME);
3✔
1571
    }
1572

1573
    private static createFlagAtLeastOne(instructionLine: uinteger | null, start: Position, end: Position, flagName: string, flagValue: string): Diagnostic {
1574
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagAtLeastOne(flagName, flagValue), ValidationCode.FLAG_AT_LEAST_ONE);
2✔
1575
    }
1576

1577
    private static createFlagDuplicate(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1578
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagDuplicate(flag), ValidationCode.FLAG_DUPLICATE);
22✔
1579
    }
1580

1581
    private static createFlagInvalidDuration(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1582
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagInvalidDuration(flag), ValidationCode.FLAG_INVALID_DURATION);
9✔
1583
    }
1584

1585
    private static createFlagLessThan1ms(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1586
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagLessThan1ms(flag), ValidationCode.FLAG_LESS_THAN_1MS);
27✔
1587
    }
1588

1589
    private static createFlagMissingDuration(instructionLine: uinteger | null, start: Position, end: Position, duration: string): Diagnostic {
1590
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagMissingDuration(duration), ValidationCode.FLAG_MISSING_DURATION);
18✔
1591
    }
1592

1593
    private static createFlagInvalidFrom(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1594
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagInvalidFromValue(flag), ValidationCode.FLAG_INVALID_FROM_VALUE);
3✔
1595
    }
1596

1597
    private static createFlagExpectedBooleanValue(instructionLine: uinteger | null, range: Range, flag: string, value: string): Diagnostic {
1598
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_FlagExpectedBooleanValue(flag, value), ValidationCode.FLAG_EXPECTED_BOOLEAN_VALUE);
4✔
1599
    }
1600

1601
    private static createFlagMissingValue(instructionLine: uinteger | null, range: Range, flag: string): Diagnostic {
1602
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_FlagMissingValue(flag), ValidationCode.FLAG_MISSING_VALUE);
17✔
1603
    }
1604

1605
    private static createUnknownAddFlag(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1606
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagUnknown(flag), ValidationCode.UNKNOWN_ADD_FLAG);
7✔
1607
    }
1608

1609
    private static createUnknownCopyFlag(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1610
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagUnknown(flag), ValidationCode.UNKNOWN_COPY_FLAG);
8✔
1611
    }
1612

1613
    private static createUnknownFromFlag(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1614
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagUnknown(flag), ValidationCode.UNKNOWN_FROM_FLAG);
3✔
1615
    }
1616

1617
    private static createUnknownHealthcheckFlag(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1618
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagUnknown(flag), ValidationCode.UNKNOWN_HEALTHCHECK_FLAG);
13✔
1619
    }
1620

1621
    private static createFlagUnknownUnit(instructionLine: uinteger | null, range: Range, unit: string, duration: string): Diagnostic {
1622
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_FlagUnknownUnit(unit, duration), ValidationCode.FLAG_UNKNOWN_UNIT);
15✔
1623
    }
1624

1625
    private static createBaseNameEmpty(instructionLine: uinteger | null, range: Range, name: string): Diagnostic {
1626
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_BaseNameEmpty(name), ValidationCode.BASE_NAME_EMPTY);
3✔
1627
    }
1628

1629
    private static createInvalidAs(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1630
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InvalidAs(), ValidationCode.INVALID_AS);
1✔
1631
    }
1632

1633
    private static createInvalidPort(instructionLine: uinteger | null, range: Range, port: string): Diagnostic {
1634
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InvalidPort(port), ValidationCode.INVALID_PORT);
27✔
1635
    }
1636

1637
    private static createInvalidProto(instructionLine: uinteger | null, start: Position, end: Position, protocol: string): Diagnostic {
1638
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InvalidProto(protocol), ValidationCode.INVALID_PROTO);
4✔
1639
    }
1640

1641
    private static createInvalidReferenceFormat(instructionLine: uinteger | null, range: Range): Diagnostic {
1642
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InvalidReferenceFormat(), ValidationCode.INVALID_REFERENCE_FORMAT);
42✔
1643
    }
1644

1645
    private static createInvalidStopSignal(instructionLine: uinteger | null, start: Position, end: Position, signal: string): Diagnostic {
1646
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InvalidSignal(signal), ValidationCode.INVALID_SIGNAL);
7✔
1647
    }
1648

1649
    private static createInvalidSyntax(instructionLine: uinteger | null, start: Position, end: Position, syntax: string): Diagnostic {
1650
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InvalidSyntax(syntax), ValidationCode.INVALID_SYNTAX);
2✔
1651
    }
1652

1653
    private static createMissingArgument(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1654
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionMissingArgument(), ValidationCode.ARGUMENT_MISSING);
652✔
1655
    }
1656

1657
    private static createExtraArgument(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1658
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionExtraArgument(), ValidationCode.ARGUMENT_EXTRA);
8✔
1659
    }
1660

1661
    private static createHealthcheckNoneUnnecessaryArgument(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1662
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_HealthcheckNoneUnnecessaryArgument(), ValidationCode.ARGUMENT_UNNECESSARY);
2✔
1663
    }
1664

1665
    private static createADDDestinationNotDirectory(instructionLine: uinteger | null, range: Range): Diagnostic {
1666
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_ADDDestinationNotDirectory(), ValidationCode.INVALID_DESTINATION);
8✔
1667
    }
1668

1669
    private static createADDRequiresAtLeastTwoArguments(instructionLine: uinteger | null, range: Range): Diagnostic {
1670
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_ADDRequiresAtLeastTwoArguments(), ValidationCode.ARGUMENT_REQUIRES_AT_LEAST_TWO);
61✔
1671
    }
1672

1673
    private static createCOPYDestinationNotDirectory(instructionLine: uinteger | null, range: Range): Diagnostic {
1674
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_COPYDestinationNotDirectory(), ValidationCode.INVALID_DESTINATION);
13✔
1675
    }
1676

1677
    private static createCOPYRequiresAtLeastTwoArguments(instructionLine: uinteger | null, range: Range): Diagnostic {
1678
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_COPYRequiresAtLeastTwoArguments(), ValidationCode.ARGUMENT_REQUIRES_AT_LEAST_TWO);
61✔
1679
    }
1680

1681
    private static createENVRequiresTwoArguments(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1682
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_ENVRequiresTwoArguments(), ValidationCode.ARGUMENT_REQUIRES_TWO);
2✔
1683
    }
1684

1685
    private static createHEALTHCHECKRequiresAtLeastOneArgument(instructionLine: uinteger | null, range: Range): Diagnostic {
1686
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_HEALTHCHECKRequiresAtLeastOneArgument(), ValidationCode.ARGUMENT_REQUIRES_AT_LEAST_ONE);
53✔
1687
    }
1688

1689
    private static createHealthcheckCmdArgumentMissing(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1690
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_HealthcheckCmdArgumentMissing(), ValidationCode.HEALTHCHECK_CMD_ARGUMENT_MISSING);
3✔
1691
    }
1692

1693
    private static createHealthcheckTypeUnknown(instructionLine: uinteger | null, range: Range, type: string): Diagnostic {
1694
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_HealthcheckTypeUnknown(type), ValidationCode.UNKNOWN_TYPE);
5✔
1695
    }
1696

1697
    private static createOnbuildChainingDisallowed(instructionLine: uinteger | null, range: Range): Diagnostic {
1698
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_OnbuildChainingDisallowed(), ValidationCode.ONBUILD_CHAINING_DISALLOWED);
5✔
1699
    }
1700

1701
    private static createOnbuildTriggerDisallowed(instructionLine: uinteger | null, range: Range, trigger: string): Diagnostic {
1702
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_OnbuildTriggerDisallowed(trigger), ValidationCode.ONBUILD_TRIGGER_DISALLOWED);
14✔
1703
    }
1704

1705
    private static createShellJsonForm(instructionLine: uinteger | null, range: Range): Diagnostic {
1706
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_ShellJsonForm(), ValidationCode.SHELL_JSON_FORM);
13✔
1707
    }
1708

1709
    private static createShellRequiresOne(instructionLine: uinteger | null, range: Range): Diagnostic {
1710
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_ShellRequiresOne(), ValidationCode.SHELL_REQUIRES_ONE);
3✔
1711
    }
1712

1713
    private static createRequiresOneOrThreeArguments(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1714
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionRequiresOneOrThreeArguments(), ValidationCode.ARGUMENT_REQUIRES_ONE_OR_THREE);
14✔
1715
    }
1716

1717
    private static createNoSourceImage(start: Position, end: Position): DockerfileDiagnostic {
1718
        return Validator.createError(null, start, end, Validator.getDiagnosticMessage_NoSourceImage(), ValidationCode.NO_SOURCE_IMAGE);
17✔
1719
    }
1720

1721
    private static createSyntaxMissingEquals(instructionLine: uinteger | null, start: Position, end: Position, argument: string): Diagnostic {
1722
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_SyntaxMissingEquals(argument), ValidationCode.SYNTAX_MISSING_EQUALS);
4✔
1723
    }
1724

1725
    private static createSyntaxMissingSingleQuote(instructionLine: uinteger | null, start: Position, end: Position, argument: string): Diagnostic {
1726
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_SyntaxMissingSingleQuote(argument), ValidationCode.SYNTAX_MISSING_SINGLE_QUOTE);
12✔
1727
    }
1728

1729
    private static createSyntaxMissingDoubleQuote(instructionLine: uinteger | null, start: Position, end: Position, argument: string): Diagnostic {
1730
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_SyntaxMissingDoubleQuote(argument), ValidationCode.SYNTAX_MISSING_DOUBLE_QUOTE);
27✔
1731
    }
1732

1733
    private static createSyntaxMissingNames(instructionLine: uinteger | null, start: Position, end: Position, instruction: string): Diagnostic {
1734
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_SyntaxMissingNames(instruction), ValidationCode.SYNTAX_MISSING_NAMES);
10✔
1735
    }
1736

1737
    private static createVariableUnsupportedModifier(instructionLine: uinteger | null, range: Range, variable: string, modifier: string): Diagnostic {
1738
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_VariableModifierUnsupported(variable, modifier), ValidationCode.UNSUPPORTED_MODIFIER);
45✔
1739
    }
1740

1741
    private static createUnknownInstruction(instructionLine: uinteger | null, start: Position, end: Position, instruction: string): Diagnostic {
1742
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionUnknown(instruction), ValidationCode.UNKNOWN_INSTRUCTION);
22✔
1743
    }
1744

1745
    private static createError(instructionLine: uinteger | null, start: Position, end: Position, description: string, code?: ValidationCode, tags?: DiagnosticTag[]): DockerfileDiagnostic {
1746
        return Validator.createDiagnostic(DiagnosticSeverity.Error, instructionLine, start, end, description, code, tags);
1,342✔
1747
    }
1748

1749
    private static createJSONInSingleQuotes(instructionLine: uinteger | null, range: Range, severity: ValidationSeverity | undefined): Diagnostic | null {
1750
        if (severity === ValidationSeverity.ERROR) {
36✔
1751
            return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InstructionJSONInSingleQuotes(), ValidationCode.JSON_IN_SINGLE_QUOTES);
6✔
1752
        } else if (severity === ValidationSeverity.WARNING) {
30✔
1753
            return Validator.createWarning(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InstructionJSONInSingleQuotes(), ValidationCode.JSON_IN_SINGLE_QUOTES);
24✔
1754
        }
1755
        return null;
6✔
1756
    }
1757

1758
    private static createEmptyContinuationLine(start: Position, end: Position, severity: ValidationSeverity | undefined): Diagnostic | null {
1759
        if (severity === ValidationSeverity.ERROR) {
523✔
1760
            return Validator.createError(null, start, end, Validator.getDiagnosticMessage_EmptyContinuationLine(), ValidationCode.EMPTY_CONTINUATION_LINE);
7✔
1761
        } else if (severity === ValidationSeverity.WARNING) {
516✔
1762
            return Validator.createWarning(null, start, end, Validator.getDiagnosticMessage_EmptyContinuationLine(), ValidationCode.EMPTY_CONTINUATION_LINE);
6✔
1763
        }
1764
        return null;
510✔
1765
    }
1766

1767
    private createMultipleInstructions(instructionLine: uinteger | null, range: Range, severity: ValidationSeverity | undefined, instruction: string): Diagnostic | null {
1768
        if (severity === ValidationSeverity.ERROR) {
40✔
1769
            return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InstructionMultiple(instruction), ValidationCode.MULTIPLE_INSTRUCTIONS, [DiagnosticTag.Unnecessary]);
12✔
1770
        } else if (severity === ValidationSeverity.WARNING) {
28✔
1771
            return Validator.createWarning(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InstructionMultiple(instruction), ValidationCode.MULTIPLE_INSTRUCTIONS, [DiagnosticTag.Unnecessary]);
24✔
1772
        }
1773
        return null;
4✔
1774
    }
1775

1776
    private createLowercaseDirective(start: Position, end: Position): Diagnostic | null {
1777
        if (this.settings.directiveCasing === ValidationSeverity.ERROR) {
6✔
1778
            return Validator.createError(null, start, end, Validator.getDiagnosticMessage_DirectiveCasing(), ValidationCode.CASING_DIRECTIVE);
1✔
1779
        } else if (this.settings.directiveCasing === ValidationSeverity.WARNING) {
5✔
1780
            return Validator.createWarning(null, start, end, Validator.getDiagnosticMessage_DirectiveCasing(), ValidationCode.CASING_DIRECTIVE);
4✔
1781
        }
1782
        return null;
1✔
1783
    }
1784

1785
    createUppercaseInstruction(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic | null {
1786
        if (this.settings.instructionCasing === ValidationSeverity.ERROR) {
85✔
1787
            return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionCasing(), ValidationCode.CASING_INSTRUCTION);
2✔
1788
        } else if (this.settings.instructionCasing === ValidationSeverity.WARNING) {
83✔
1789
            return Validator.createWarning(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionCasing(), ValidationCode.CASING_INSTRUCTION);
78✔
1790
        }
1791
        return null;
5✔
1792
    }
1793

1794
    createMaintainerDeprecated(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic | null {
1795
        if (this.settings.deprecatedMaintainer === ValidationSeverity.ERROR) {
82✔
1796
            return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_DeprecatedMaintainer(), ValidationCode.DEPRECATED_MAINTAINER, [DiagnosticTag.Deprecated]);
2✔
1797
        } else if (this.settings.deprecatedMaintainer === ValidationSeverity.WARNING) {
80✔
1798
            return Validator.createWarning(instructionLine, start, end, Validator.getDiagnosticMessage_DeprecatedMaintainer(), ValidationCode.DEPRECATED_MAINTAINER, [DiagnosticTag.Deprecated]);
4✔
1799
        }
1800
        return null;
76✔
1801
    }
1802

1803
    private createWORKDIRNotAbsolute(instructionLine: uinteger | null,range: Range): Diagnostic | null {
1804
        if (this.settings.instructionWorkdirRelative === ValidationSeverity.ERROR) {
32✔
1805
            return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_WORKDIRPathNotAbsolute(), ValidationCode.WORKDIR_IS_NOT_ABSOLUTE);
8✔
1806
        } else if (this.settings.instructionWorkdirRelative === ValidationSeverity.WARNING) {
24✔
1807
            return Validator.createWarning(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_WORKDIRPathNotAbsolute(), ValidationCode.WORKDIR_IS_NOT_ABSOLUTE);
16✔
1808
        }
1809
        return null;
8✔
1810
    }
1811

1812
    static createWarning(instructionLine: uinteger | null, start: Position, end: Position, description: string, code?: ValidationCode, tags?: DiagnosticTag[]): Diagnostic {
1813
        return Validator.createDiagnostic(DiagnosticSeverity.Warning, instructionLine, start, end, description, code, tags);
156✔
1814
    }
1815

1816
    static createDiagnostic(severity: DiagnosticSeverity, instructionLine: uinteger | null, start: Position, end: Position, description: string, code?: ValidationCode, tags?: DiagnosticTag[]): DockerfileDiagnostic {
1817
        return {
1,498✔
1818
            instructionLine: instructionLine,
1819
            range: {
1820
                start: start,
1821
                end: end
1822
            },
1823
            message: description,
1824
            severity: severity,
1825
            code: code,
1826
            tags: tags,
1827
            source: "dockerfile-utils"
1828
        };
1829
    }
1830
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc