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

rcjsuen / dockerfile-utils / 6136550538

10 Sep 2023 10:20AM UTC coverage: 99.111% (+0.004%) from 99.107%
6136550538

push

github

rcjsuen
Update versions of GitHub Actions

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

610 of 620 branches covered (0.0%)

Branch coverage included in aggregate %.

1063 of 1068 relevant lines covered (99.53%)

771.17 hits per line

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

99.22
/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
export class Validator {
1✔
42

43
    private document: TextDocument;
44

45
    private settings: ValidatorSettings = {
5,706✔
46
        deprecatedMaintainer: ValidationSeverity.WARNING,
47
        directiveCasing: ValidationSeverity.WARNING,
48
        emptyContinuationLine: ValidationSeverity.WARNING,
49
        instructionCasing: ValidationSeverity.WARNING,
50
        instructionCmdMultiple: ValidationSeverity.WARNING,
51
        instructionEntrypointMultiple: ValidationSeverity.WARNING,
52
        instructionHealthcheckMultiple: ValidationSeverity.WARNING,
53
        instructionJSONInSingleQuotes: ValidationSeverity.WARNING,
54
        instructionWorkdirRelative: ValidationSeverity.WARNING
55
    }
56

57
    constructor(settings?: ValidatorSettings) {
58
        if (settings) {
5,706✔
59
            this.settings = settings;
5,652✔
60
        }
61
    }
62

63
    private checkDirectives(dockerfile: Dockerfile, problems: Diagnostic[]) {
64
        const duplicatedEscapes = [];
5,710✔
65
        for (const directive of dockerfile.getDirectives()) {
5,710✔
66
            if (directive.getDirective() === Directive.escape) {
49✔
67
                duplicatedEscapes.push(directive);
46✔
68
            }
69
        }
70

71
        if (duplicatedEscapes.length > 1) {
5,710✔
72
            // multiple escape parser directives have been found
73
            for (let i = 1; i < duplicatedEscapes.length; i++) {
1✔
74
                problems.push(Validator.createDuplicatedEscapeDirective(duplicatedEscapes[i].getNameRange().start, duplicatedEscapes[i].getValueRange().end));
1✔
75
            }
76
            return
1✔
77
        }
78

79
        for (const directive of dockerfile.getDirectives()) {
5,709✔
80
            const directiveName = directive.getDirective();
47✔
81
            if (directiveName === Directive.escape) {
47✔
82
                const value = directive.getValue();
44✔
83
                if (value !== '\\' && value !== '`' && value !== "") {
44✔
84
                    // if the directive's value is invalid or isn't the empty string, flag it
85
                    const range = directive.getValueRange();
4✔
86
                    problems.push(Validator.createInvalidEscapeDirective(range.start, range.end, value));
4✔
87
                }
88
    
89
                if (directive.getName() !== Directive.escape) {
44✔
90
                    const range = directive.getNameRange();
6✔
91
                    const diagnostic = this.createLowercaseDirective(range.start, range.end);
6✔
92
                    if (diagnostic) {
6✔
93
                        problems.push(diagnostic);
5✔
94
                    }
95
                }
96
            }
97
        }
98
    }
99

100
    /**
101
     * Checks the arguments of the given instruction.
102
     * 
103
     * @param instruction the instruction to validate
104
     * @param problems an array of identified problems in the document
105
     * @param expectedArgCount an array of expected number of arguments
106
     *                         for the instruction, if its length is 1
107
     *                         and its value is -1, any number of
108
     *                         arguments greater than zero is valid
109
     * @param validate the function to use to validate an argument
110
     * @param createIncompleteDiagnostic the function to use to create a diagnostic
111
     *                                   if the number of arguments is incorrect
112
     */
113
    private checkArguments(instruction: Instruction, problems: Diagnostic[], expectedArgCount: number[],
114
        validate: Function, createIncompleteDiagnostic?: Function): void {
115
        let args = instruction instanceof PropertyInstruction ? instruction.getPropertyArguments() : instruction.getArguments();
10,269✔
116
        if (args.length === 0) {
10,269✔
117
            if (instruction.getKeyword() !== Keyword.RUN) {
656✔
118
                // all instructions are expected to have at least one argument
119
                const range = instruction.getInstructionRange();
602✔
120
                problems.push(Validator.createMissingArgument(range.start.line, range.start, range.end));
602✔
121
            }
122
        } else if (expectedArgCount[0] === -1) {
9,613✔
123
            for (let i = 0; i < args.length; i++) {
3,695✔
124
                let createInvalidDiagnostic = validate(i, args[i].getValue(), args[i].getRange());
4,249✔
125
                if (createInvalidDiagnostic) {
4,249!
126
                    let range = args[i].getRange();
×
127
                    problems.push(createInvalidDiagnostic(instruction.getInstructionRange().start.line, range.start, range.end, args[i].getValue()));
×
128
                }
129
            }
130
        } else {
131
            for (let i = 0; i < expectedArgCount.length; i++) {
5,918✔
132
                if (expectedArgCount[i] === args.length) {
5,967✔
133
                    for (let j = 0; j < args.length; j++) {
5,896✔
134
                        let range = args[j].getRange();
5,966✔
135
                        let createInvalidDiagnostic = validate(j, args[j].getValue(), range);
5,966✔
136
                        if (createInvalidDiagnostic instanceof Function) {
5,966✔
137
                            problems.push(createInvalidDiagnostic(instruction.getInstructionRange().start.line, range.start, range.end, args[j].getValue()));
5✔
138
                        } else if (createInvalidDiagnostic !== null) {
5,961✔
139
                            problems.push(createInvalidDiagnostic);
47✔
140
                        }
141
                    }
142
                    return;
5,896✔
143
                }
144
            }
145

146
            let range = args[args.length - 1].getRange();
22✔
147
            if (createIncompleteDiagnostic) {
22✔
148
                problems.push(createIncompleteDiagnostic(instruction.getInstructionRange().start.line, range.start, range.end));
14✔
149
            } else {
150
                problems.push(Validator.createExtraArgument(instruction.getInstructionRange().start.line, range.start, range.end));
8✔
151
            }
152
        }
153
    }
154

155
    private checkVariables(instruction: Instruction, problems: Diagnostic[]): void {
156
        for (let variable of instruction.getVariables()) {
11,492✔
157
            let modifier = variable.getModifier();
217✔
158
            if (modifier !== null) {
217✔
159
                switch (instruction.getKeyword()) {
116✔
160
                    case Keyword.CMD:
134✔
161
                    case Keyword.ENTRYPOINT:
162
                    case Keyword.RUN:
163
                        // allow shell expansions to go through for RUN instructions
164
                        break;
18✔
165
                    default:
166
                        if (modifier === "") {
98✔
167
                            problems.push(Validator.createVariableUnsupportedModifier(instruction.getRange().start.line, variable.getRange(), variable.toString(), modifier));
15✔
168
                        } else if (modifier !== '+' && modifier !== '-' && modifier !== '?') {
83✔
169
                            problems.push(Validator.createVariableUnsupportedModifier(instruction.getRange().start.line, variable.getModifierRange(), variable.toString(), modifier));
30✔
170
                        }
171
                        break;
98✔
172
                }
173
            }
174
        }
175
    }
176

177
    private checkProperty(document: TextDocument, escapeChar: string, keyword: string, instructionLine: uinteger, property: Property, firstProperty: boolean, optionalValue: boolean, problems: Diagnostic[]): void {
178
        let name = property.getName();
3,102✔
179
        if (name === "") {
3,102✔
180
            let range = property.getRange();
10✔
181
            problems.push(Validator.createSyntaxMissingNames(instructionLine, range.start, range.end, keyword));
10✔
182
        } else if (name.indexOf('=') !== -1) {
3,092✔
183
            let nameRange = property.getNameRange();
12✔
184
            let unescapedName = document.getText(nameRange);
12✔
185
            let index = unescapedName.indexOf('=');
12✔
186
            if (unescapedName.charAt(0) === '\'') {
12✔
187
                problems.push(Validator.createSyntaxMissingSingleQuote(instructionLine, nameRange.start, document.positionAt(document.offsetAt(nameRange.start) + index), unescapedName.substring(0, unescapedName.indexOf('='))));
6✔
188
            } else if (unescapedName.charAt(0) === '"') {
6!
189
                problems.push(Validator.createSyntaxMissingDoubleQuote(instructionLine, nameRange.start, document.positionAt(document.offsetAt(nameRange.start) + index), unescapedName.substring(0, unescapedName.indexOf('='))));
6✔
190
            }
191
            return;
12✔
192
        }
193

194
        let value = property.getValue();
3,090✔
195
        if (value === null) {
3,090✔
196
            if (!optionalValue) {
35✔
197
                let range = property.getNameRange();
6✔
198
                if (firstProperty) {
6✔
199
                    problems.push(Validator.createENVRequiresTwoArguments(instructionLine, range.start, range.end));
2✔
200
                } else {
201
                    problems.push(Validator.createSyntaxMissingEquals(instructionLine, range.start, range.end, name));
4✔
202
                }
203
            }
204
        } else if (value.charAt(0) === '"') {
3,055✔
205
            let found = false;
24✔
206
            for (let i = 1; i < value.length; i++) {
24✔
207
                switch (value.charAt(i)) {
102✔
208
                    case escapeChar:
15✔
209
                        i++;
12✔
210
                        break;
12✔
211
                    case '"':
212
                        if (i === value.length - 1) {
3!
213
                            found = true;
3✔
214
                        }
215
                        break;
3✔
216
                }
217
            }
218

219
            if (!found) {
24✔
220
                let range = property.getValueRange();
21✔
221
                problems.push(Validator.createSyntaxMissingDoubleQuote(instructionLine, range.start, range.end, property.getUnescapedValue()));
21✔
222
            }
223
        } else if (value.charAt(0) === '\'' && value.charAt(value.length - 1) !== '\'') {
3,031✔
224
            let range = property.getValueRange();
6✔
225
            problems.push(Validator.createSyntaxMissingSingleQuote(instructionLine, range.start, range.end, value));
6✔
226
        }
227
    }
228

229
    validate(document: TextDocument): Diagnostic[] {
230
        this.document = document;
5,710✔
231
        let problems: DockerfileDiagnostic[] = [];
5,710✔
232
        let dockerfile = DockerfileParser.parse(document.getText());
5,710✔
233
        this.checkDirectives(dockerfile, problems);
5,710✔
234
        let instructions = dockerfile.getInstructions();
5,710✔
235
        if (instructions.length === 0 || dockerfile.getARGs().length === instructions.length) {
5,710✔
236
            // no instructions in this file, or only ARGs
237
            problems.push(Validator.createNoSourceImage(document.positionAt(0), document.positionAt(0)));
11✔
238
        }
239

240
        let cmds: Cmd[] = [];
5,710✔
241
        let entrypoints: Entrypoint[] = [];
5,710✔
242
        let healthchecks: Healthcheck[] = [];
5,710✔
243
        for (let instruction of instructions) {
5,710✔
244
            if (instruction instanceof Cmd) {
11,492✔
245
                cmds.push(instruction);
112✔
246
            } else if (instruction instanceof Entrypoint) {
11,380✔
247
                entrypoints.push(instruction);
97✔
248
            } else if (instruction instanceof Healthcheck) {
11,283✔
249
                healthchecks.push(instruction);
259✔
250
            } else if (instruction instanceof From) {
11,024✔
251
                this.createDuplicatesDiagnostics(problems, this.settings.instructionCmdMultiple, "CMD", cmds);
5,801✔
252
                this.createDuplicatesDiagnostics(problems, this.settings.instructionEntrypointMultiple, "ENTRYPOINT", entrypoints);
5,801✔
253
                this.createDuplicatesDiagnostics(problems, this.settings.instructionHealthcheckMultiple, "HEALTHCHECK", healthchecks);
5,801✔
254

255
                cmds = [];
5,801✔
256
                entrypoints = [];
5,801✔
257
                healthchecks = [];
5,801✔
258
            }
259
        }
260
        this.createDuplicatesDiagnostics(problems, this.settings.instructionCmdMultiple, "CMD", cmds);
5,710✔
261
        this.createDuplicatesDiagnostics(problems, this.settings.instructionEntrypointMultiple, "ENTRYPOINT", entrypoints);
5,710✔
262
        this.createDuplicatesDiagnostics(problems, this.settings.instructionHealthcheckMultiple, "HEALTHCHECK", healthchecks);
5,710✔
263
        this.createDuplicateBuildStageNameDiagnostics(problems, dockerfile.getFROMs());
5,710✔
264

265
        let escapeChar = dockerfile.getEscapeCharacter();
5,710✔
266
        let hasFrom = false;
5,710✔
267
        for (let instruction of dockerfile.getInstructions()) {
5,710✔
268
            let keyword = instruction.getKeyword();
11,492✔
269
            if (keyword === "FROM") {
11,492✔
270
                hasFrom = true;
5,801✔
271
            } else if (!hasFrom && keyword !== "ARG") {
5,691✔
272
                // first non-ARG instruction is not a FROM
273
                let range = instruction.getInstructionRange();
6✔
274
                problems.push(Validator.createNoSourceImage(range.start, range.end));
6✔
275
                hasFrom = true;
6✔
276
            }
277
            this.validateInstruction(document, escapeChar, instruction, keyword, false, problems);
11,492✔
278
            this.checkVariables(instruction, problems);
11,492✔
279
        }
280

281
        for (let instruction of dockerfile.getOnbuildTriggers()) {
5,710✔
282
            this.validateInstruction(document, escapeChar, instruction, instruction.getKeyword(), true, problems);
98✔
283
        }
284

285
        const ignoredLines = [];
5,710✔
286
        for (const comment of dockerfile.getComments()) {
5,710✔
287
            if (comment.getContent() === "dockerfile-utils: ignore") {
80✔
288
                ignoredLines.push(comment.getRange().start.line);
3✔
289
            }
290
        }
291

292
        problemsCheck: for (let i = 0; i < problems.length; i++) {
5,710✔
293
            if (problems[i].instructionLine !== null) {
1,491✔
294
                for (const ignoredLine of ignoredLines) {
1,451✔
295
                    if (ignoredLine + 1 === problems[i].instructionLine) {
4✔
296
                        problems.splice(i, 1);
3✔
297
                        i--;
3✔
298
                        continue problemsCheck;
3✔
299
                    }
300
                }
301
            }
302
        }
303
        return problems;
5,710✔
304
    }
305

306
    /**
307
     * Retrieves the line numbers that corresponds to the content of
308
     * here-documents in the given instruction. The line numbers are
309
     * zero-based.
310
     * 
311
     * @param instruction the instruction to check
312
     * @returns an array of line numbers where content of
313
     *          here-documents are defined
314
     */
315
    private getHeredocLines(instruction: Instruction): number[] {
316
        if (instruction instanceof Copy || instruction instanceof Run) {
3,043✔
317
            const lines: number[] = [];
99✔
318
            for (const heredoc of instruction.getHeredocs()) {
99✔
319
                const range = heredoc.getContentRange();
19✔
320
                if (range !== null) {
19!
321
                    for (let i = range.start.line; i <= range.end.line; i++) {
19✔
322
                        lines.push(i);
25✔
323
                    }
324
                 }
325
            }
326
            return lines;
99✔
327
        }
328
        return []
2,944✔
329
    }
330

331
    private validateInstruction(document: TextDocument, escapeChar: string, instruction: Instruction, keyword: string, isTrigger: boolean, problems: Diagnostic[]): void {
332
        if (KEYWORDS.indexOf(keyword) === -1) {
11,590✔
333
            let range = instruction.getInstructionRange();
22✔
334
            // invalid instruction found
335
            problems.push(Validator.createUnknownInstruction(range.start.line, range.start, range.end, keyword));
22✔
336
        } else {
337
            if (keyword !== instruction.getInstruction()) {
11,568✔
338
                let range = instruction.getInstructionRange();
85✔
339
                // warn about uppercase convention if the keyword doesn't match the actual instruction
340
                let diagnostic = this.createUppercaseInstruction(range.start.line, range.start, range.end);
85✔
341
                if (diagnostic) {
85✔
342
                    problems.push(diagnostic);
80✔
343
                }
344
            }
345

346
            if (keyword === "MAINTAINER") {
11,568✔
347
                let range = instruction.getInstructionRange();
82✔
348
                let diagnostic = this.createMaintainerDeprecated(range.start.line, range.start, range.end);
82✔
349
                if (diagnostic) {
82✔
350
                    problems.push(diagnostic);
6✔
351
                }
352
            }
353

354
            const fullRange = instruction.getRange();
11,568✔
355
            if (fullRange.start.line !== fullRange.end.line && !isTrigger) {
11,568✔
356
                // if the instruction spans multiple lines, check for empty newlines
357
                const content = document.getText();
3,043✔
358
                const endingLine = fullRange.end.line;
3,043✔
359
                const skippedLines = this.getHeredocLines(instruction);
3,043✔
360
                let skipIndex = 0;
3,043✔
361
                let start = -1;
3,043✔
362
                for (let i = fullRange.start.line; i <= endingLine; i++) {
3,043✔
363
                    if (i === skippedLines[skipIndex]) {
6,850✔
364
                        skipIndex++;
25✔
365
                        continue;
25✔
366
                    }
367
                    const lineContent = content.substring(document.offsetAt(Position.create(i, 0)), document.offsetAt(Position.create(i + 1, 0)));
6,825✔
368
                    if (lineContent.trim().length === 0) {
6,825✔
369
                        if (start === -1) {
529✔
370
                            start = i;
523✔
371
                            continue;
523✔
372
                        }
373
                    } else if (start !== -1) {
6,296✔
374
                        const diagnostic = Validator.createEmptyContinuationLine(Position.create(start, 0), Position.create(i, 0), this.settings.emptyContinuationLine);
58✔
375
                        if (diagnostic) {
58✔
376
                            problems.push(diagnostic);
11✔
377
                        }
378
                        start = -1;
58✔
379
                    }
380
                }
381

382
                if (start !== -1) {
3,043✔
383
                    const diagnostic = Validator.createEmptyContinuationLine(Position.create(start, 0), Position.create(endingLine + 1, 0), this.settings.emptyContinuationLine);
465✔
384
                    if (diagnostic) {
465✔
385
                        problems.push(diagnostic);
2✔
386
                    }
387
                    start = -1;
465✔
388
                }
389
            }
390

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

545
                    const validFlags = ["interval", "retries", "start-period", "timeout", "start-interval"];
239✔
546
                    for (const flag of healthcheckFlags) {
239✔
547
                        const flagName = flag.getName();
136✔
548
                        if (validFlags.indexOf(flagName) === -1) {
136✔
549
                            const range = flag.getRange();
13✔
550
                            problems.push(Validator.createUnknownHealthcheckFlag(healthcheckInstructionRange.start.line, range.start, flagName === "" ? range.end : flag.getNameRange().end, flag.getName()));
13✔
551
                        } else if (flagName === "retries") {
123✔
552
                            const value = flag.getValue();
10✔
553
                            if (value) {
10✔
554
                                const valueRange = flag.getValueRange();
8✔
555
                                const integer = parseInt(value);
8✔
556
                                // test for NaN or numbers with decimals
557
                                if (isNaN(integer) || value.indexOf('.') !== -1) {
8✔
558
                                    problems.push(Validator.createInvalidSyntax(healthcheckInstructionRange.start.line, valueRange.start, valueRange.end, value));
2✔
559
                                } else if (integer < 1) {
6✔
560
                                    problems.push(Validator.createFlagAtLeastOne(healthcheckInstructionRange.start.line, valueRange.start, valueRange.end, "--retries", integer.toString()));
2✔
561
                                }
562
                            }
563
                        }
564
                    }
565

566
                    this.checkFlagValue(healthcheckInstructionRange.start.line, healthcheckFlags, validFlags, problems);
239✔
567
                    this.checkFlagDuration(healthcheckInstructionRange.start.line, healthcheckFlags, ["interval", "start-period", "timeout", "start-interval"], problems);
239✔
568
                    this.checkDuplicateFlags(healthcheckInstructionRange.start.line, healthcheckFlags, validFlags, problems);
239✔
569
                    break;
239✔
570
                case "ONBUILD":
571
                    this.checkArguments(instruction, problems, [-1], function (): any {
153✔
572
                        return null;
293✔
573
                    });
574
                    let onbuild = instruction as Onbuild;
153✔
575
                    let trigger = onbuild.getTrigger();
153✔
576
                    switch (trigger) {
153✔
577
                        case "FROM":
24✔
578
                        case "MAINTAINER":
579
                            problems.push(Validator.createOnbuildTriggerDisallowed(onbuild.getInstructionRange().start.line, onbuild.getTriggerRange(), trigger));
14✔
580
                            break;
14✔
581
                        case "ONBUILD":
582
                            problems.push(Validator.createOnbuildChainingDisallowed(onbuild.getInstructionRange().start.line, onbuild.getTriggerRange()));
5✔
583
                            break;
5✔
584
                    }
585
                    break;
153✔
586
                case "SHELL":
587
                    this.checkArguments(instruction, problems, [-1], function (): any {
110✔
588
                        return null;
175✔
589
                    });
590
                    this.checkJSON(document, instruction as JSONInstruction, problems);
110✔
591
                    break;
110✔
592
                case "STOPSIGNAL":
593
                    this.checkArguments(instruction, problems, [1], function (_index: number, argument: string) {
212✔
594
                        if (argument.indexOf("SIG") === 0 || argument.indexOf('$') != -1) {
154✔
595
                            return null;
97✔
596
                        }
597

598
                        for (var i = 0; i < argument.length; i++) {
57✔
599
                            if ('0' > argument.charAt(i) || '9' < argument.charAt(i)) {
57✔
600
                                return Validator.createInvalidStopSignal;
4✔
601
                            }
602
                        }
603
                        return null;
53✔
604
                    });
605
                    let stopsignalArgs = instruction.getExpandedArguments();
212✔
606
                    if (stopsignalArgs.length === 1) {
212✔
607
                        let value = stopsignalArgs[0].getValue();
154✔
608
                        let variables = instruction.getVariables();
154✔
609
                        if (variables.length === 0) {
154✔
610
                            if (value.indexOf('$') !== -1) {
137✔
611
                                const instructionRange = instruction.getInstructionRange();
2✔
612
                                let range = stopsignalArgs[0].getRange();
2✔
613
                                problems.push(Validator.createInvalidStopSignal(instructionRange.start.line, range.start, range.end, value));
2✔
614
                            }
615
                        } else {
616
                            for (let variable of variables) {
17✔
617
                                let variableRange = variable.getRange();
17✔
618
                                let variableDefinition = this.document.getText().substring(
17✔
619
                                    this.document.offsetAt(variableRange.start),
620
                                    this.document.offsetAt(variableRange.end)
621
                                );
622
                                // an un-expanded variable is here
623
                                if (value.includes(variableDefinition) && !variable.isBuildVariable() && !variable.isDefined()) {
17✔
624
                                    const instructionRange = instruction.getInstructionRange();
1✔
625
                                    let range = stopsignalArgs[0].getRange();
1✔
626
                                    problems.push(Validator.createInvalidStopSignal(instructionRange.start.line, range.start, range.end, ""));
1✔
627
                                    break;
1✔
628
                                }
629
                            }
630
                        }
631
                    }
632
                    break;
212✔
633
                case "EXPOSE":
634
                    let exposeArgs = instruction.getArguments();
588✔
635
                    let exposeExpandedArgs = instruction.getExpandedArguments();
588✔
636
                    if (exposeExpandedArgs.length === 0) {
588✔
637
                        let range = instruction.getInstructionRange();
50✔
638
                        problems.push(Validator.createMissingArgument(range.start.line, range.start, range.end));
50✔
639
                    } else {
640
                        const regex = /^([0-9])+(-[0-9]+)?(:([0-9])+(-[0-9]*)?)?(\/(\w*))?(\/\w*)*$/;
538✔
641
                        argCheck: for (let i = 0; i < exposeExpandedArgs.length; i++) {
538✔
642
                            let value = exposeExpandedArgs[i].getValue()
542✔
643
                            if (value.charAt(0) === '"' && value.charAt(value.length - 1) === '"') {
542✔
644
                                value = value.substring(1, value.length - 1);
2✔
645
                            }
646
                            const match = regex.exec(value);
542✔
647
                            if (match) {
542✔
648
                                if (match[7]) {
511✔
649
                                    const protocol = match[7].toLowerCase();
292✔
650
                                    if (protocol !== "" && protocol !== "tcp" && protocol !== "udp" && protocol !== "sctp") {
292✔
651
                                        const range = exposeExpandedArgs[i].getRange();
4✔
652
                                        const rangeStart = this.document.offsetAt(range.start);
4✔
653
                                        const rawArg = this.document.getText().substring(
4✔
654
                                            rangeStart, this.document.offsetAt(range.end)
655
                                        );
656
                                        const start = rangeStart + rawArg.indexOf(match[7].substring(0, 1));
4✔
657
                                        const end = protocol.length === 1 ? rangeStart + start + 1 : rangeStart + rawArg.length;
4✔
658
                                        problems.push(Validator.createInvalidProto(instruction.getInstructionRange().start.line, this.document.positionAt(start), this.document.positionAt(end), match[7]));
4✔
659
                                    }
660
                                }
661
                            } else {
662
                                // see if we're referencing a variable here
663
                                if (value.charAt(0) === '$') {
31✔
664
                                    continue argCheck;
4✔
665
                                }
666
                                problems.push(Validator.createInvalidPort(instruction.getInstructionRange().start.line, exposeExpandedArgs[i].getRange(), value));
27✔
667
                            }
668
                        }
669
                    }
670
                    break;
588✔
671
                case "ADD":
672
                    const add = instruction as Add;
152✔
673
                    const addFlags = add.getFlags();
152✔
674
                    const addInstructionRange = instruction.getInstructionRange();
152✔
675
                    for (let flag of addFlags) {
152✔
676
                        const name = flag.getName();
38✔
677
                        const flagRange = flag.getRange();
38✔
678
                        if (name === "") {
38✔
679
                            problems.push(Validator.createUnknownAddFlag(addInstructionRange.start.line, flagRange.start, flagRange.end, name));
1✔
680
                        } else if (name === "link" || name === "keep-git-dir") {
37✔
681
                            const problem = this.checkFlagBoolean(addInstructionRange.start.line, flag);
14✔
682
                            if (problem !== null) {
14✔
683
                                problems.push(problem);
3✔
684
                            }
685
                        } else if (name !== "chmod" && name !== "chown" && name !== "checksum") {
23✔
686
                            let range = flag.getNameRange();
6✔
687
                            problems.push(Validator.createUnknownAddFlag(addInstructionRange.start.line, flagRange.start, range.end, name));
6✔
688
                        }
689
                    }
690
                    const addDestinationDiagnostic = this.checkDestinationIsDirectory(add, Validator.createADDRequiresAtLeastTwoArguments, Validator.createADDDestinationNotDirectory);
152✔
691
                    if (addDestinationDiagnostic !== null) {
152✔
692
                        problems.push(addDestinationDiagnostic);
69✔
693
                    }
694
                    this.checkFlagValue(addInstructionRange.start.line, addFlags, ["chmod", "chown", "checksum"], problems);
152✔
695
                    this.checkDuplicateFlags(addInstructionRange.start.line, addFlags, ["chmod", "chown", "checksum", "keep-git-dir", "link"], problems);
152✔
696
                    this.checkJSONQuotes(instruction, problems);
152✔
697
                    break;
152✔
698
                case "COPY":
699
                    const copyInstructionRange = instruction.getInstructionRange();
163✔
700
                    let copy = instruction as Copy;
163✔
701
                    let flags = copy.getFlags();
163✔
702
                    if (flags.length > 0) {
163✔
703
                        for (let flag of flags) {
32✔
704
                            const name = flag.getName();
39✔
705
                            const flagRange = flag.getRange();
39✔
706
                            if (name === "") {
39✔
707
                                problems.push(Validator.createUnknownCopyFlag(copyInstructionRange.start.line, flagRange.start, flagRange.end, name));
1✔
708
                            } else if (name === "link") {
38✔
709
                                const problem = this.checkFlagBoolean(copyInstructionRange.start.line, flag);
9✔
710
                                if (problem !== null) {
9✔
711
                                    problems.push(problem);
2✔
712
                                }
713
                            } else if (name !== "chmod" && name !== "chown" && name !== "from") {
29✔
714
                                let range = flag.getNameRange();
7✔
715
                                problems.push(Validator.createUnknownCopyFlag(copyInstructionRange.start.line, flagRange.start, range.end, name));
7✔
716
                            }
717
                        }
718

719
                        let flag = copy.getFromFlag();
32✔
720
                        if (flag) {
32✔
721
                            let value = flag.getValue();
7✔
722
                            if (value !== null) {
7✔
723
                                let regexp = new RegExp(/^[a-zA-Z0-9].*$/);
6✔
724
                                if (!regexp.test(value)) {
6✔
725
                                    let range = value === "" ? flag.getRange() : flag.getValueRange();
3✔
726
                                    problems.push(Validator.createFlagInvalidFrom(copyInstructionRange.start.line, range.start, range.end, value));
3✔
727
                                }
728
                            }
729
                        }
730
                    }
731
                    const copyDestinationDiagnostic = this.checkDestinationIsDirectory(copy, Validator.createCOPYRequiresAtLeastTwoArguments, Validator.createCOPYDestinationNotDirectory);
163✔
732
                    if (copyDestinationDiagnostic !== null) {
163✔
733
                        problems.push(copyDestinationDiagnostic);
74✔
734
                    }
735
                    this.checkFlagValue(copyInstructionRange.start.line, flags, ["chmod", "chown", "from"], problems);
163✔
736
                    this.checkDuplicateFlags(copyInstructionRange.start.line, flags, ["chmod", "chown", "from", "link"], problems);
163✔
737
                    this.checkJSONQuotes(instruction, problems);
163✔
738
                    break;
163✔
739
                case "WORKDIR":
740
                    this.checkArguments(instruction, problems, [-1], function (): any {
235✔
741
                        return null;
201✔
742
                    });
743

744
                    let content = instruction.getArgumentsContent();
235✔
745
                    if (content) {
235✔
746
                        // strip out any surrounding quotes
747
                        const first = content.substring(0, 1);
185✔
748
                        const last = content.substring(content.length - 1);
185✔
749
                        if ((first === '\'' && last === '\'') || (first === '"' && last === '"')) {
185✔
750
                            content = content.substring(1, content.length - 1);
24✔
751
                        }
752
                        let regexp = new RegExp(/^(\$|([a-zA-Z](\$|:(\$|\\|\/)))).*$/);
185✔
753
                        if (!content.startsWith('/') && !regexp.test(content)) {
185✔
754
                            let problem = this.createWORKDIRNotAbsolute(instruction.getInstructionRange().start.line, instruction.getArgumentsRange());
32✔
755
                            if (problem) {
32✔
756
                                problems.push(problem);
24✔
757
                            }
758
                        }
759
                    }
760
                    break;
235✔
761
                default:
762
                    this.checkArguments(instruction, problems, [-1], function (): any {
209✔
763
                        return null;
109✔
764
                    });
765
                    break;
209✔
766
            }
767
        }
768
    }
769

770
    private hasHeredocs(args: Argument[]): boolean {
771
        for (const arg of args) {
62✔
772
            if (arg.getValue().startsWith("<<")) {
142✔
773
                return true;
28✔
774
            }
775
        }
776
        return false;
34✔
777
    }
778

779
    private getDestinationArgument(args: Argument[]): Argument {
780
        if (this.hasHeredocs(args)) {
33✔
781
            const initialLine = args[0].getRange().start.line;
16✔
782
            let candidate: Argument | null = null;
16✔
783
            for (let i = 1; i < args.length; i++) {
16✔
784
                if (args[i].getRange().start.line === initialLine) {
44✔
785
                    candidate = args[i];
28✔
786
                } else {
787
                    // stop searching once we're on another line
788
                    break;
16✔
789
                }
790
            }
791
            return candidate;
16✔
792
        }
793
        return args[args.length - 1];
17✔
794
    }
795

796
    private checkDestinationIsDirectory(instruction: JSONInstruction, requiresTwoArgumentsFunction: (instructionLine: uinteger, range: Range) => Diagnostic, notDirectoryFunction: (instructionLine: uinteger, range: Range) => Diagnostic): Diagnostic | null {
797
        if (instruction.getClosingBracket()) {
315✔
798
            return this.checkJsonDestinationIsDirectory(instruction, requiresTwoArgumentsFunction, notDirectoryFunction);
59✔
799
        }
800

801
        const args = instruction.getArguments();
256✔
802
        if (args.length === 1) {
256✔
803
            return requiresTwoArgumentsFunction(instruction.getInstructionRange().start.line, args[0].getRange());
4✔
804
        } else if (args.length === 0) {
252✔
805
            const instructionRange = instruction.getInstructionRange();
104✔
806
            return requiresTwoArgumentsFunction(instructionRange.start.line, instructionRange);
104✔
807
        } else if (args.length > 2) {
148✔
808
            const lastArg = this.getDestinationArgument(args);
33✔
809
            if (lastArg === null) {
33✔
810
                const instructionRange = instruction.getInstructionRange();
4✔
811
                return requiresTwoArgumentsFunction(instructionRange.start.line, instructionRange);
4✔
812
            } else if (this.hasHeredocs(args)) {
29✔
813
                return null;
12✔
814
            }
815
            const variables = instruction.getVariables();
17✔
816
            if (variables.length !== 0) {
17✔
817
                const lastJsonStringOffset = this.document.offsetAt(lastArg.getRange().end);
4✔
818
                const lastVarOffset = this.document.offsetAt(variables[variables.length - 1].getRange().end);
4✔
819
                if (lastJsonStringOffset === lastVarOffset || lastJsonStringOffset -1 === lastVarOffset) {
4!
820
                    return null;
4✔
821
                }
822
            }
823
            const destination = lastArg.getValue();
13✔
824
            const lastChar = destination.charAt(destination.length - 1);
13✔
825
            if (lastChar !== '\\' && lastChar !== '/') {
13✔
826
                return notDirectoryFunction(instruction.getInstructionRange().start.line, lastArg.getRange());
8✔
827
            }
828
        }
829
        return null;
120✔
830
    }
831

832
    private createDuplicatesDiagnostics(problems: Diagnostic[], severity: ValidationSeverity, instruction: string, instructions: Instruction[]): void {
833
        if (instructions.length > 1) {
34,533✔
834
            // decrement length by 1 because we want to ignore the last one
835
            for (let i = 0; i < instructions.length - 1; i++) {
40✔
836
                const instructionRange = instructions[i].getInstructionRange();
40✔
837
                const diagnostic = this.createMultipleInstructions(instructionRange.start.line, instructionRange, severity, instruction);
40✔
838
                if (diagnostic) {
40✔
839
                    problems.push(diagnostic);
36✔
840
                }
841
            }
842
        }
843
    }
844

845
    private createDuplicateBuildStageNameDiagnostics(problems: Diagnostic[], froms: From[]): void {
846
        const names: any = {};
5,710✔
847
        for (let from of froms) {
5,710✔
848
            let name = from.getBuildStage();
5,801✔
849
            if (name !== null) {
5,801✔
850
                name = name.toLowerCase();
36✔
851
                if (names[name] === undefined) {
36✔
852
                    names[name] = [from];
32✔
853
                } else {
854
                    names[name].push(from);
4✔
855
                }
856
            }
857
        }
858

859
        for (const name in names) {
5,710✔
860
            // duplicates found
861
            if (names[name].length > 1) {
32✔
862
                for (const from of names[name]) {
4✔
863
                    problems.push(Validator.createDuplicateBuildStageName(from.getInstructionRange().start.line, from.getBuildStageRange(), name));
8✔
864
                }
865
            }
866
        }
867
    }
868

869
    private checkJsonDestinationIsDirectory(instruction: JSONInstruction, requiresTwoArgumentsFunction: (instructionLine: uinteger, range: Range) => Diagnostic, notDirectoryFunction: (instructionLine: uinteger, range: Range) => Diagnostic): Diagnostic | null {
870
        const jsonStrings = instruction.getJSONStrings();
59✔
871
        if (jsonStrings.length === 0) {
59✔
872
            return requiresTwoArgumentsFunction(instruction.getInstructionRange().start.line, instruction.getArgumentsRange());
4✔
873
        } else if (jsonStrings.length === 1) {
55✔
874
            return requiresTwoArgumentsFunction(instruction.getInstructionRange().start.line, jsonStrings[0].getJSONRange());
6✔
875
        } else if (jsonStrings.length > 2) {
49✔
876
            const lastJsonString = jsonStrings[jsonStrings.length - 1];
37✔
877
            const variables = instruction.getVariables();
37✔
878
            if (variables.length !== 0) {
37✔
879
                const lastVar = variables[variables.length - 1];
12✔
880
                const lastJsonStringOffset = this.document.offsetAt(lastJsonString.getRange().end);
12✔
881
                const lastVarOffset = this.document.offsetAt(lastVar.getRange().end);
12✔
882
                if (lastJsonStringOffset === lastVarOffset || lastJsonStringOffset -1 === lastVarOffset) {
12!
883
                    return null;
12✔
884
                }
885
            }
886
            const destination = lastJsonString.getValue();
25✔
887
            const lastChar = destination.charAt(destination.length - 2);
25✔
888
            if (lastChar !== '\\' && lastChar !== '/') {
25✔
889
                return notDirectoryFunction(instruction.getInstructionRange().start.line, jsonStrings[jsonStrings.length - 1].getJSONRange());
13✔
890
            }
891
        }
892
        return null;
24✔
893
    }
894

895
    private checkFlagValue(instructionLine: uinteger, flags: Flag[], validFlagNames: string[], problems: Diagnostic[]): void {
896
        for (let flag of flags) {
6,360✔
897
            let flagName = flag.getName();
219✔
898
            // only validate flags with the right name
899
            if (flag.getValue() === null && validFlagNames.indexOf(flagName) !== -1) {
219✔
900
                problems.push(Validator.createFlagMissingValue(instructionLine, flag.getNameRange(), flagName));
12✔
901
            }
902
        }
903
    }
904

905
    /**
906
     * Checks that the given boolean flag is valid. A boolean flag should
907
     * either have no value defined (--flag) or the value should
908
     * case-insensitively be either "true" or "false" (--flag==tRUe is
909
     * valid).
910
     */
911
    private checkFlagBoolean(instructionLine: uinteger, flag: Flag): Diagnostic | null {
912
        const flagValue = flag.getValue();
23✔
913
        if (flagValue === "") {
23✔
914
            return Validator.createFlagMissingValue(instructionLine, flag.getNameRange(), flag.getName());
2✔
915
        } else if (flagValue !== null) {
21✔
916
            const convertedFlagValue = flagValue.toLowerCase();
18✔
917
            if (convertedFlagValue !== "true" && convertedFlagValue !== "false") {
18✔
918
                return Validator.createFlagExpectedBooleanValue(instructionLine, flag.getValueRange(), flag.getName(), flagValue);
3✔
919
            }
920
        }
921
        return null;
18✔
922
    }
923

924
    private checkFlagDuration(instructionLine: uinteger, flags: Flag[], validFlagNames: string[], problems: Diagnostic[]): void {
925
        flagCheck: for (let flag of flags) {
239✔
926
            let flagName = flag.getName();
136✔
927
            // only validate flags with the right name
928
            if (validFlagNames.indexOf(flagName) !== -1) {
136✔
929
                let value = flag.getValue();
113✔
930
                if (value !== null && value.length !== 0) {
113✔
931
                    switch (value.charAt(0)) {
105✔
932
                        case '0':
735✔
933
                        case '1':
934
                        case '2':
935
                        case '3':
936
                        case '4':
937
                        case '5':
938
                        case '6':
939
                        case '7':
940
                        case '8':
941
                        case '9':
942
                        case '.':
943
                        case '-':
944
                            break;
99✔
945
                        default:
946
                            let range = flag.getValueRange();
6✔
947
                            problems.push(Validator.createFlagInvalidDuration(instructionLine, range.start, range.end, value));
6✔
948
                            continue flagCheck;
6✔
949
                    }
950

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

1101
                    if (!durationSpecified) {
60✔
1102
                        let range = flag.getValueRange();
9✔
1103
                        problems.push(Validator.createFlagMissingDuration(instructionLine, range.start, range.end, value));
9✔
1104
                    } else if (duration < 1) {
51✔
1105
                        let range = flag.getNameRange();
15✔
1106
                        problems.push(Validator.createFlagLessThan1ms(instructionLine, range.start, range.end, flagName));
15✔
1107
                    }
1108
                }
1109
            }
1110
        }
1111
    }
1112

1113
    private static isNumberRelated(character: string) {
1114
        switch (character) {
70✔
1115
            case '.':
270✔
1116
            case '0':
1117
            case '1':
1118
            case '2':
1119
            case '3':
1120
            case '4':
1121
            case '5':
1122
            case '6':
1123
            case '7':
1124
            case '8':
1125
            case '9':
1126
                return true;
36✔
1127
        }
1128
        return false;
34✔
1129
    }
1130

1131
    private checkDuplicateFlags(instructionLine: uinteger, flags: Flag[], validFlags: string[], problems: Diagnostic[]): void {
1132
        let flagNames = flags.map(function (flag) {
554✔
1133
            return flag.getName();
213✔
1134
        });
1135
        for (let validFlag of validFlags) {
554✔
1136
            let index = flagNames.indexOf(validFlag);
2,607✔
1137
            let lastIndex = flagNames.lastIndexOf(validFlag);
2,607✔
1138
            if (index !== lastIndex) {
2,607✔
1139
                let range = flags[index].getNameRange();
10✔
1140
                problems.push(Validator.createFlagDuplicate(instructionLine, range.start, range.end, flagNames[index]));
10✔
1141
                range = flags[lastIndex].getNameRange();
10✔
1142
                problems.push(Validator.createFlagDuplicate(instructionLine, range.start, range.end, flagNames[index]));
10✔
1143
            }
1144
        }
1145
    }
1146

1147
    private checkJSON(document: TextDocument, instruction: JSONInstruction, problems: Diagnostic[]) {
1148
        let argsContent = instruction.getArgumentsContent();
110✔
1149
        if (argsContent === null) {
110✔
1150
            return;
50✔
1151
        }
1152

1153
        let argsRange = instruction.getArgumentsRange();
60✔
1154
        let args = instruction.getArguments();
60✔
1155
        if ((args.length === 1 && args[0].getValue() === "[]") ||
60✔
1156
            (args.length === 2 && args[0].getValue() === '[' && args[1].getValue() === ']')) {
1157
            problems.push(Validator.createShellRequiresOne(instruction.getInstructionRange().start.line, argsRange));
3✔
1158
            return;
3✔
1159
        }
1160

1161
        const closing = instruction.getClosingBracket();
57✔
1162
        if (closing) {
57✔
1163
            let content = document.getText();
45✔
1164
            content = content.substring(
45✔
1165
                document.offsetAt(instruction.getOpeningBracket().getRange().end),
1166
                document.offsetAt(closing.getRange().start)
1167
            );
1168
            content = content.trim();
45✔
1169
            if (content.charAt(content.length - 1) !== '"') {
45✔
1170
                problems.push(Validator.createShellJsonForm(instruction.getInstructionRange().start.line, argsRange));
1✔
1171
            }
1172
        } else {
1173
            problems.push(Validator.createShellJsonForm(instruction.getInstructionRange().start.line, argsRange));
12✔
1174
        }
1175
    }
1176

1177
    private checkJSONQuotes(instruction: Instruction, problems: Diagnostic[]) {
1178
        let argsContent = instruction.getArgumentsContent();
768✔
1179
        if (argsContent === null) {
768✔
1180
            return;
308✔
1181
        }
1182

1183
        let argsRange = instruction.getArgumentsRange();
460✔
1184
        let args = instruction.getArguments();
460✔
1185
        if ((args.length === 1 && args[0].getValue() === "[]") ||
460✔
1186
            (args.length === 2 && args[0].getValue() === '[' && args[1].getValue() === ']')) {
1187
            return;
4✔
1188
        }
1189

1190
        let jsonLike = false;
456✔
1191
        let last: string = null;
456✔
1192
        let quoted = false;
456✔
1193
        argsCheck: for (let i = 0; i < argsContent.length; i++) {
456✔
1194
            switch (argsContent.charAt(i)) {
1,361✔
1195
                case '[':
1,488✔
1196
                    if (last === null) {
147✔
1197
                        last = '[';
145✔
1198
                        jsonLike = true;
145✔
1199
                    }
1200
                    break;
147✔
1201
                case '\'':
1202
                    if (last === '[' || last === ',') {
151✔
1203
                        quoted = true;
77✔
1204
                        last = '\'';
77✔
1205
                        continue;
77✔
1206
                    } else if (last === '\'') {
74✔
1207
                        if (quoted) {
73✔
1208
                            // quoted string done
1209
                            quoted = false;
72✔
1210
                        } else {
1211
                            // should be a , or a ]
1212
                            break argsCheck;
1✔
1213
                        }
1214
                    } else {
1215
                        break argsCheck;
1✔
1216
                    }
1217
                    break;
72✔
1218
                case ',':
1219
                    if (!jsonLike) {
54✔
1220
                        break argsCheck;
1✔
1221
                    } else if (!quoted) {
53✔
1222
                        if (last === '\'') {
46✔
1223
                            last = ','
45✔
1224
                        } else {
1225
                            break argsCheck;
1✔
1226
                        }
1227
                    }
1228
                    break;
52✔
1229
                case ']':
1230
                    if (!quoted) {
49✔
1231
                        if (last === '\'' || last === ',') {
37✔
1232
                            last = null;
36✔
1233
                            const problem = Validator.createJSONInSingleQuotes(instruction.getInstructionRange().start.line, argsRange, this.settings.instructionJSONInSingleQuotes);
36✔
1234
                            if (problem) {
36✔
1235
                                problems.push(problem);
30✔
1236
                            }
1237
                        }
1238
                        break argsCheck;
37✔
1239
                    }
1240
                    break;
12✔
1241
                case ' ':
1242
                case '\t':
1243
                    break;
127✔
1244
                default:
1245
                    if (!quoted) {
833✔
1246
                        break argsCheck;
407✔
1247
                    }
1248
                    break;
426✔
1249
            }
1250
        }
1251
    }
1252

1253
    private static dockerProblems = {
1✔
1254
        "baseNameEmpty": "base name (${0}) should not be blank",
1255

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

1260
        "noSourceImage": "No source image provided with `FROM`",
1261

1262
        "emptyContinuationLine": "Empty continuation line",
1263

1264
        "fromRequiresOneOrThreeArguments": "FROM requires either one or three arguments",
1265

1266
        "invalidAs": "Second argument should be AS",
1267
        "invalidPort": "Invalid containerPort: ${0}",
1268
        "invalidProtocol": "Invalid proto: ${0}",
1269
        "invalidReferenceFormat": "invalid reference format",
1270
        "invalidStopSignal": "Invalid signal: ${0}",
1271
        "invalidSyntax": "parsing \"${0}\": invalid syntax",
1272
        "invalidDestination": "When using ${0} with more than one source file, the destination must be a directory and end with a / or a \\",
1273

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

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

1282
        "flagAtLeastOne": "${0} must be at least 1 (not ${1})",
1283
        "flagDuplicate": "Duplicate flag specified: ${0}",
1284
        "flagInvalidDuration": "time: invalid duration ${0}",
1285
        "flagInvalidFrom": "invalid from flag value ${0}: invalid reference format",
1286
        "flagExpectedBooleanValue": "expecting boolean value for flag ${0}, not: ${1}",
1287
        "flagLessThan1ms": "Interval \"${0}\" cannot be less than 1ms",
1288
        "flagMissingDuration": "time: missing unit in duration ${0}",
1289
        "flagMissingValue": "Missing a value on flag: ${0}",
1290
        "flagUnknown": "Unknown flag: ${0}",
1291
        "flagUnknownUnit": "time: unknown unit ${0} in duration ${1}",
1292

1293
        "instructionExtraArgument": "Instruction has an extra argument",
1294
        "instructionMissingArgument": "Instruction has no arguments",
1295
        "instructionMultiple": "There can only be one ${0} instruction in a Dockerfile or build stage. Only the last one will have an effect.",
1296
        "instructionRequiresOneArgument": "${0} requires exactly one argument",
1297
        "instructionRequiresAtLeastOneArgument": "${0} requires at least one argument",
1298
        "instructionRequiresAtLeastTwoArguments": "${0} requires at least two arguments",
1299
        "instructionRequiresTwoArguments": "${0} must have two arguments",
1300
        "instructionUnnecessaryArgument": "${0} takes no arguments",
1301
        "instructionUnknown": "Unknown instruction: ${0}",
1302
        "instructionCasing": "Instructions should be written in uppercase letters",
1303
        "instructionJSONInSingleQuotes": "Instruction written as a JSON array but is using single quotes instead of double quotes",
1304

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

1307
        "onbuildChainingDisallowed": "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed",
1308
        "onbuildTriggerDisallowed": "${0} isn't allowed as an ONBUILD trigger",
1309

1310
        "shellJsonForm": "SHELL requires the arguments to be in JSON form",
1311
        "shellRequiresOne": "SHELL requires at least one argument",
1312

1313
        "deprecatedMaintainer": "MAINTAINER has been deprecated",
1314

1315
        "healthcheckCmdArgumentMissing": "Missing command after HEALTHCHECK CMD",
1316
        "healthcheckTypeUnknown": "Unknown type\"${0}\" in HEALTHCHECK (try CMD)",
1317

1318
        "workdirPathNotAbsolute": "WORKDIR paths should be absolute"
1319
    };
1320

1321
    private static formatMessage(text: string, ...variables: string[]): string {
1322
        for (let i = 0; i < variables.length; i++) {
1,193✔
1323
            text = text.replace("${" + i + "}", variables[i]);
1,275✔
1324
        }
1325
        return text;
1,193✔
1326
    }
1327

1328
    public static getDiagnosticMessage_DirectiveCasing() {
1329
        return Validator.dockerProblems["directiveCasing"];
10✔
1330
    }
1331

1332
    public static getDiagnosticMessage_DirectiveEscapeDuplicated() {
1333
        return Validator.dockerProblems["directiveEscapeDuplicated"];
2✔
1334
    }
1335

1336
    public static getDiagnosticMessage_DirectiveEscapeInvalid(value: string) {
1337
        return Validator.formatMessage(Validator.dockerProblems["directiveEscapeInvalid"], value);
8✔
1338
    }
1339

1340
    public static getDiagnosticMessage_NoSourceImage() {
1341
        return Validator.dockerProblems["noSourceImage"];
34✔
1342
    }
1343

1344
    public static getDiagnosticMessage_EmptyContinuationLine() {
1345
        return Validator.dockerProblems["emptyContinuationLine"];
26✔
1346
    }
1347

1348
    public static getDiagnosticMessage_DuplicateBuildStageName(name: string) {
1349
        return Validator.formatMessage(Validator.dockerProblems["duplicateBuildStageName"], name);
16✔
1350
    }
1351

1352
    public static getDiagnosticMessage_InvalidBuildStageName(name: string) {
1353
        return Validator.formatMessage(Validator.dockerProblems["invalidBuildStageName"], name);
6✔
1354
    }
1355

1356
    public static getDiagnosticMessage_FlagAtLeastOne(flagName: string, flagValue: string) {
1357
        return Validator.formatMessage(Validator.dockerProblems["flagAtLeastOne"], flagName, flagValue);
4✔
1358
    }
1359

1360
    public static getDiagnosticMessage_FlagDuplicate(flag: string) {
1361
        return Validator.formatMessage(Validator.dockerProblems["flagDuplicate"], flag);
40✔
1362
    }
1363

1364
    public static getDiagnosticMessage_FlagInvalidDuration(flag: string) {
1365
        return Validator.formatMessage(Validator.dockerProblems["flagInvalidDuration"], flag);
18✔
1366
    }
1367

1368
    public static getDiagnosticMessage_FlagLessThan1ms(flag: string) {
1369
        return Validator.formatMessage(Validator.dockerProblems["flagLessThan1ms"], flag);
54✔
1370
    }
1371

1372
    public static getDiagnosticMessage_FlagMissingDuration(duration: string) {
1373
        return Validator.formatMessage(Validator.dockerProblems["flagMissingDuration"], duration);
36✔
1374
    }
1375

1376
    public static getDiagnosticMessage_FlagInvalidFromValue(value: string): string {
1377
        return Validator.formatMessage(Validator.dockerProblems["flagInvalidFrom"], value);
6✔
1378
    }
1379

1380
    public static getDiagnosticMessage_FlagExpectedBooleanValue(flag: string, value: string): string {
1381
        return Validator.formatMessage(Validator.dockerProblems["flagExpectedBooleanValue"], flag, value);
6✔
1382
    }
1383

1384
    public static getDiagnosticMessage_FlagMissingValue(flag: string) {
1385
        return Validator.formatMessage(Validator.dockerProblems["flagMissingValue"], flag);
28✔
1386
    }
1387

1388
    public static getDiagnosticMessage_FlagUnknown(flag: string) {
1389
        return Validator.formatMessage(Validator.dockerProblems["flagUnknown"], flag);
62✔
1390
    }
1391

1392
    public static getDiagnosticMessage_FlagUnknownUnit(unit: string, duration: string) {
1393
        return Validator.formatMessage(Validator.dockerProblems["flagUnknownUnit"], unit, duration);
30✔
1394
    }
1395

1396
    public static getDiagnosticMessage_BaseNameEmpty(name: string) {
1397
        return Validator.formatMessage(Validator.dockerProblems["baseNameEmpty"], name);
4✔
1398
    }
1399

1400
    public static getDiagnosticMessage_InvalidAs() {
1401
        return Validator.dockerProblems["invalidAs"];
2✔
1402
    }
1403

1404
    public static getDiagnosticMessage_InvalidPort(port: string) {
1405
        return Validator.formatMessage(Validator.dockerProblems["invalidPort"], port);
54✔
1406
    }
1407

1408
    public static getDiagnosticMessage_InvalidProto(protocol: string) {
1409
        return Validator.formatMessage(Validator.dockerProblems["invalidProtocol"], protocol);
8✔
1410
    }
1411

1412
    public static getDiagnosticMessage_InvalidReferenceFormat() {
1413
        return Validator.dockerProblems["invalidReferenceFormat"];
84✔
1414
    }
1415

1416
    public static getDiagnosticMessage_InvalidSignal(signal: string) {
1417
        return Validator.formatMessage(Validator.dockerProblems["invalidStopSignal"], signal);
14✔
1418
    }
1419

1420
    public static getDiagnosticMessage_InvalidSyntax(syntax: string) {
1421
        return Validator.formatMessage(Validator.dockerProblems["invalidSyntax"], syntax);
4✔
1422
    }
1423

1424
    public static getDiagnosticMessage_InstructionExtraArgument() {
1425
        return Validator.dockerProblems["instructionExtraArgument"];
16✔
1426
    }
1427

1428
    public static getDiagnosticMessage_InstructionMissingArgument() {
1429
        return Validator.dockerProblems["instructionMissingArgument"];
1,304✔
1430
    }
1431

1432
    public static getDiagnosticMessage_ADDDestinationNotDirectory() {
1433
        return Validator.formatMessage(Validator.dockerProblems["invalidDestination"], "ADD");
16✔
1434
    }
1435

1436
    public static getDiagnosticMessage_ADDRequiresAtLeastTwoArguments() {
1437
        return Validator.formatMessage(Validator.dockerProblems["instructionRequiresAtLeastTwoArguments"], "ADD");
122✔
1438
    }
1439

1440
    public static getDiagnosticMessage_COPYDestinationNotDirectory() {
1441
        return Validator.formatMessage(Validator.dockerProblems["invalidDestination"], "COPY");
26✔
1442
    }
1443

1444
    public static getDiagnosticMessage_COPYRequiresAtLeastTwoArguments() {
1445
        return Validator.formatMessage(Validator.dockerProblems["instructionRequiresAtLeastTwoArguments"], "COPY");
122✔
1446
    }
1447

1448
    public static getDiagnosticMessage_HEALTHCHECKRequiresAtLeastOneArgument() {
1449
        return Validator.formatMessage(Validator.dockerProblems["instructionRequiresAtLeastOneArgument"], "HEALTHCHECK");
106✔
1450
    }
1451

1452
    public static getDiagnosticMessage_ENVRequiresTwoArguments() {
1453
        return Validator.formatMessage(Validator.dockerProblems["instructionRequiresTwoArguments"], "ENV");
4✔
1454
    }
1455

1456
    public static getDiagnosticMessage_InstructionRequiresOneOrThreeArguments() {
1457
        return Validator.dockerProblems["fromRequiresOneOrThreeArguments"];
28✔
1458
    }
1459

1460
    public static getDiagnosticMessage_HealthcheckNoneUnnecessaryArgument() {
1461
        return Validator.formatMessage(Validator.dockerProblems["instructionUnnecessaryArgument"], "HEALTHCHECK NONE");
4✔
1462
    }
1463

1464
    public static getDiagnosticMessage_InstructionMultiple(instruction: string) {
1465
        return Validator.formatMessage(Validator.dockerProblems["instructionMultiple"], instruction);
72✔
1466
    }
1467

1468
    public static getDiagnosticMessage_InstructionUnknown(instruction: string) {
1469
        return Validator.formatMessage(Validator.dockerProblems["instructionUnknown"], instruction);
41✔
1470
    }
1471

1472
    public static getDiagnosticMessage_SyntaxMissingEquals(argument: string) {
1473
        return Validator.formatMessage(Validator.dockerProblems["syntaxMissingEquals"], argument);
8✔
1474
    }
1475

1476
    public static getDiagnosticMessage_SyntaxMissingNames(instruction: string) {
1477
        return Validator.formatMessage(Validator.dockerProblems["syntaxMissingNames"], instruction);
20✔
1478
    }
1479

1480
    public static getDiagnosticMessage_SyntaxMissingSingleQuote(key: string) {
1481
        return Validator.formatMessage(Validator.dockerProblems["syntaxMissingSingleQuote"], key);
24✔
1482
    }
1483

1484
    public static getDiagnosticMessage_SyntaxMissingDoubleQuote(key: string) {
1485
        return Validator.formatMessage(Validator.dockerProblems["syntaxMissingDoubleQuote"], key);
54✔
1486
    }
1487

1488
    public static getDiagnosticMessage_InstructionCasing() {
1489
        return Validator.dockerProblems["instructionCasing"];
160✔
1490
    }
1491

1492
    public static getDiagnosticMessage_InstructionJSONInSingleQuotes() {
1493
        return Validator.dockerProblems["instructionJSONInSingleQuotes"];
60✔
1494
    }
1495

1496
    public static getDiagnosticMessage_OnbuildChainingDisallowed() {
1497
        return Validator.dockerProblems["onbuildChainingDisallowed"];
10✔
1498
    }
1499

1500
    public static getDiagnosticMessage_OnbuildTriggerDisallowed(trigger: string) {
1501
        return Validator.formatMessage(Validator.dockerProblems["onbuildTriggerDisallowed"], trigger);
28✔
1502
    }
1503

1504
    public static getDiagnosticMessage_VariableModifierUnsupported(variable: string, modifier: string) {
1505
        return Validator.formatMessage(Validator.dockerProblems["variableModifierUnsupported"], variable, modifier);
90✔
1506
    }
1507

1508
    public static getDiagnosticMessage_ShellJsonForm() {
1509
        return Validator.dockerProblems["shellJsonForm"];
26✔
1510
    }
1511

1512
    public static getDiagnosticMessage_ShellRequiresOne() {
1513
        return Validator.dockerProblems["shellRequiresOne"];
6✔
1514
    }
1515

1516
    public static getDiagnosticMessage_DeprecatedMaintainer() {
1517
        return Validator.dockerProblems["deprecatedMaintainer"];
12✔
1518
    }
1519

1520
    public static getDiagnosticMessage_HealthcheckCmdArgumentMissing() {
1521
        return Validator.dockerProblems["healthcheckCmdArgumentMissing"];
6✔
1522
    }
1523

1524
    public static getDiagnosticMessage_HealthcheckTypeUnknown(type: string) {
1525
        return Validator.formatMessage(Validator.dockerProblems["healthcheckTypeUnknown"], type);
10✔
1526
    }
1527

1528
    public static getDiagnosticMessage_WORKDIRPathNotAbsolute() {
1529
        return Validator.formatMessage(Validator.dockerProblems["workdirPathNotAbsolute"]);
48✔
1530
    }
1531

1532
    private static createDuplicatedEscapeDirective(start: Position, end: Position): Diagnostic {
1533
        return Validator.createError(null, start, end, Validator.getDiagnosticMessage_DirectiveEscapeDuplicated(), ValidationCode.DUPLICATED_ESCAPE_DIRECTIVE, [DiagnosticTag.Unnecessary]);
1✔
1534
    }
1535

1536
    private static createInvalidEscapeDirective(start: Position, end: Position, value: string): Diagnostic {
1537
        return Validator.createError(null, start, end, Validator.getDiagnosticMessage_DirectiveEscapeInvalid(value), ValidationCode.INVALID_ESCAPE_DIRECTIVE);
4✔
1538
    }
1539

1540
    private static createDuplicateBuildStageName(instructionLine: uinteger, range: Range, name: string): Diagnostic {
1541
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_DuplicateBuildStageName(name), ValidationCode.DUPLICATE_BUILD_STAGE_NAME);
8✔
1542
    }
1543

1544
    private static createInvalidBuildStageName(instructionLine: uinteger | null, range: Range, name: string): Diagnostic {
1545
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InvalidBuildStageName(name), ValidationCode.INVALID_BUILD_STAGE_NAME);
3✔
1546
    }
1547

1548
    private static createFlagAtLeastOne(instructionLine: uinteger | null, start: Position, end: Position, flagName: string, flagValue: string): Diagnostic {
1549
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagAtLeastOne(flagName, flagValue), ValidationCode.FLAG_AT_LEAST_ONE);
2✔
1550
    }
1551

1552
    private static createFlagDuplicate(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1553
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagDuplicate(flag), ValidationCode.FLAG_DUPLICATE);
20✔
1554
    }
1555

1556
    private static createFlagInvalidDuration(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1557
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagInvalidDuration(flag), ValidationCode.FLAG_INVALID_DURATION);
9✔
1558
    }
1559

1560
    private static createFlagLessThan1ms(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1561
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagLessThan1ms(flag), ValidationCode.FLAG_LESS_THAN_1MS);
27✔
1562
    }
1563

1564
    private static createFlagMissingDuration(instructionLine: uinteger | null, start: Position, end: Position, duration: string): Diagnostic {
1565
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagMissingDuration(duration), ValidationCode.FLAG_MISSING_DURATION);
18✔
1566
    }
1567

1568
    private static createFlagInvalidFrom(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1569
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagInvalidFromValue(flag), ValidationCode.FLAG_INVALID_FROM_VALUE);
3✔
1570
    }
1571

1572
    private static createFlagExpectedBooleanValue(instructionLine: uinteger | null, range: Range, flag: string, value: string): Diagnostic {
1573
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_FlagExpectedBooleanValue(flag, value), ValidationCode.FLAG_EXPECTED_BOOLEAN_VALUE);
3✔
1574
    }
1575

1576
    private static createFlagMissingValue(instructionLine: uinteger | null, range: Range, flag: string): Diagnostic {
1577
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_FlagMissingValue(flag), ValidationCode.FLAG_MISSING_VALUE);
14✔
1578
    }
1579

1580
    private static createUnknownAddFlag(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1581
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagUnknown(flag), ValidationCode.UNKNOWN_ADD_FLAG);
7✔
1582
    }
1583

1584
    private static createUnknownCopyFlag(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1585
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagUnknown(flag), ValidationCode.UNKNOWN_COPY_FLAG);
8✔
1586
    }
1587

1588
    private static createUnknownFromFlag(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1589
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagUnknown(flag), ValidationCode.UNKNOWN_FROM_FLAG);
3✔
1590
    }
1591

1592
    private static createUnknownHealthcheckFlag(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1593
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagUnknown(flag), ValidationCode.UNKNOWN_HEALTHCHECK_FLAG);
13✔
1594
    }
1595

1596
    private static createFlagUnknownUnit(instructionLine: uinteger | null, range: Range, unit: string, duration: string): Diagnostic {
1597
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_FlagUnknownUnit(unit, duration), ValidationCode.FLAG_UNKNOWN_UNIT);
15✔
1598
    }
1599

1600
    private static createBaseNameEmpty(instructionLine: uinteger | null, range: Range, name: string): Diagnostic {
1601
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_BaseNameEmpty(name), ValidationCode.BASE_NAME_EMPTY);
2✔
1602
    }
1603

1604
    private static createInvalidAs(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1605
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InvalidAs(), ValidationCode.INVALID_AS);
1✔
1606
    }
1607

1608
    private static createInvalidPort(instructionLine: uinteger | null, range: Range, port: string): Diagnostic {
1609
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InvalidPort(port), ValidationCode.INVALID_PORT);
27✔
1610
    }
1611

1612
    private static createInvalidProto(instructionLine: uinteger | null, start: Position, end: Position, protocol: string): Diagnostic {
1613
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InvalidProto(protocol), ValidationCode.INVALID_PROTO);
4✔
1614
    }
1615

1616
    private static createInvalidReferenceFormat(instructionLine: uinteger | null, range: Range): Diagnostic {
1617
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InvalidReferenceFormat(), ValidationCode.INVALID_REFERENCE_FORMAT);
42✔
1618
    }
1619

1620
    private static createInvalidStopSignal(instructionLine: uinteger | null, start: Position, end: Position, signal: string): Diagnostic {
1621
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InvalidSignal(signal), ValidationCode.INVALID_SIGNAL);
7✔
1622
    }
1623

1624
    private static createInvalidSyntax(instructionLine: uinteger | null, start: Position, end: Position, syntax: string): Diagnostic {
1625
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InvalidSyntax(syntax), ValidationCode.INVALID_SYNTAX);
2✔
1626
    }
1627

1628
    private static createMissingArgument(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1629
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionMissingArgument(), ValidationCode.ARGUMENT_MISSING);
652✔
1630
    }
1631

1632
    private static createExtraArgument(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1633
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionExtraArgument(), ValidationCode.ARGUMENT_EXTRA);
8✔
1634
    }
1635

1636
    private static createHealthcheckNoneUnnecessaryArgument(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1637
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_HealthcheckNoneUnnecessaryArgument(), ValidationCode.ARGUMENT_UNNECESSARY);
2✔
1638
    }
1639

1640
    private static createADDDestinationNotDirectory(instructionLine: uinteger | null, range: Range): Diagnostic {
1641
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_ADDDestinationNotDirectory(), ValidationCode.INVALID_DESTINATION);
8✔
1642
    }
1643

1644
    private static createADDRequiresAtLeastTwoArguments(instructionLine: uinteger | null, range: Range): Diagnostic {
1645
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_ADDRequiresAtLeastTwoArguments(), ValidationCode.ARGUMENT_REQUIRES_AT_LEAST_TWO);
61✔
1646
    }
1647

1648
    private static createCOPYDestinationNotDirectory(instructionLine: uinteger | null, range: Range): Diagnostic {
1649
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_COPYDestinationNotDirectory(), ValidationCode.INVALID_DESTINATION);
13✔
1650
    }
1651

1652
    private static createCOPYRequiresAtLeastTwoArguments(instructionLine: uinteger | null, range: Range): Diagnostic {
1653
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_COPYRequiresAtLeastTwoArguments(), ValidationCode.ARGUMENT_REQUIRES_AT_LEAST_TWO);
61✔
1654
    }
1655

1656
    private static createENVRequiresTwoArguments(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1657
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_ENVRequiresTwoArguments(), ValidationCode.ARGUMENT_REQUIRES_TWO);
2✔
1658
    }
1659

1660
    private static createHEALTHCHECKRequiresAtLeastOneArgument(instructionLine: uinteger | null, range: Range): Diagnostic {
1661
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_HEALTHCHECKRequiresAtLeastOneArgument(), ValidationCode.ARGUMENT_REQUIRES_AT_LEAST_ONE);
53✔
1662
    }
1663

1664
    private static createHealthcheckCmdArgumentMissing(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1665
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_HealthcheckCmdArgumentMissing(), ValidationCode.HEALTHCHECK_CMD_ARGUMENT_MISSING);
3✔
1666
    }
1667

1668
    private static createHealthcheckTypeUnknown(instructionLine: uinteger | null, range: Range, type: string): Diagnostic {
1669
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_HealthcheckTypeUnknown(type), ValidationCode.UNKNOWN_TYPE);
5✔
1670
    }
1671

1672
    private static createOnbuildChainingDisallowed(instructionLine: uinteger | null, range: Range): Diagnostic {
1673
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_OnbuildChainingDisallowed(), ValidationCode.ONBUILD_CHAINING_DISALLOWED);
5✔
1674
    }
1675

1676
    private static createOnbuildTriggerDisallowed(instructionLine: uinteger | null, range: Range, trigger: string): Diagnostic {
1677
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_OnbuildTriggerDisallowed(trigger), ValidationCode.ONBUILD_TRIGGER_DISALLOWED);
14✔
1678
    }
1679

1680
    private static createShellJsonForm(instructionLine: uinteger | null, range: Range): Diagnostic {
1681
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_ShellJsonForm(), ValidationCode.SHELL_JSON_FORM);
13✔
1682
    }
1683

1684
    private static createShellRequiresOne(instructionLine: uinteger | null, range: Range): Diagnostic {
1685
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_ShellRequiresOne(), ValidationCode.SHELL_REQUIRES_ONE);
3✔
1686
    }
1687

1688
    private static createRequiresOneOrThreeArguments(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1689
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionRequiresOneOrThreeArguments(), ValidationCode.ARGUMENT_REQUIRES_ONE_OR_THREE);
14✔
1690
    }
1691

1692
    private static createNoSourceImage(start: Position, end: Position): DockerfileDiagnostic {
1693
        return Validator.createError(null, start, end, Validator.getDiagnosticMessage_NoSourceImage(), ValidationCode.NO_SOURCE_IMAGE);
17✔
1694
    }
1695

1696
    private static createSyntaxMissingEquals(instructionLine: uinteger | null, start: Position, end: Position, argument: string): Diagnostic {
1697
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_SyntaxMissingEquals(argument), ValidationCode.SYNTAX_MISSING_EQUALS);
4✔
1698
    }
1699

1700
    private static createSyntaxMissingSingleQuote(instructionLine: uinteger | null, start: Position, end: Position, argument: string): Diagnostic {
1701
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_SyntaxMissingSingleQuote(argument), ValidationCode.SYNTAX_MISSING_SINGLE_QUOTE);
12✔
1702
    }
1703

1704
    private static createSyntaxMissingDoubleQuote(instructionLine: uinteger | null, start: Position, end: Position, argument: string): Diagnostic {
1705
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_SyntaxMissingDoubleQuote(argument), ValidationCode.SYNTAX_MISSING_DOUBLE_QUOTE);
27✔
1706
    }
1707

1708
    private static createSyntaxMissingNames(instructionLine: uinteger | null, start: Position, end: Position, instruction: string): Diagnostic {
1709
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_SyntaxMissingNames(instruction), ValidationCode.SYNTAX_MISSING_NAMES);
10✔
1710
    }
1711

1712
    private static createVariableUnsupportedModifier(instructionLine: uinteger | null, range: Range, variable: string, modifier: string): Diagnostic {
1713
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_VariableModifierUnsupported(variable, modifier), ValidationCode.UNSUPPORTED_MODIFIER);
45✔
1714
    }
1715

1716
    private static createUnknownInstruction(instructionLine: uinteger | null, start: Position, end: Position, instruction: string): Diagnostic {
1717
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionUnknown(instruction), ValidationCode.UNKNOWN_INSTRUCTION);
22✔
1718
    }
1719

1720
    private static createError(instructionLine: uinteger | null, start: Position, end: Position, description: string, code?: ValidationCode, tags?: DiagnosticTag[]): DockerfileDiagnostic {
1721
        return Validator.createDiagnostic(DiagnosticSeverity.Error, instructionLine, start, end, description, code, tags);
1,335✔
1722
    }
1723

1724
    private static createJSONInSingleQuotes(instructionLine: uinteger | null, range: Range, severity: ValidationSeverity | undefined): Diagnostic | null {
1725
        if (severity === ValidationSeverity.ERROR) {
36✔
1726
            return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InstructionJSONInSingleQuotes(), ValidationCode.JSON_IN_SINGLE_QUOTES);
6✔
1727
        } else if (severity === ValidationSeverity.WARNING) {
30✔
1728
            return Validator.createWarning(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InstructionJSONInSingleQuotes(), ValidationCode.JSON_IN_SINGLE_QUOTES);
24✔
1729
        }
1730
        return null;
6✔
1731
    }
1732

1733
    private static createEmptyContinuationLine(start: Position, end: Position, severity: ValidationSeverity | undefined): Diagnostic | null {
1734
        if (severity === ValidationSeverity.ERROR) {
523✔
1735
            return Validator.createError(null, start, end, Validator.getDiagnosticMessage_EmptyContinuationLine(), ValidationCode.EMPTY_CONTINUATION_LINE);
7✔
1736
        } else if (severity === ValidationSeverity.WARNING) {
516✔
1737
            return Validator.createWarning(null, start, end, Validator.getDiagnosticMessage_EmptyContinuationLine(), ValidationCode.EMPTY_CONTINUATION_LINE);
6✔
1738
        }
1739
        return null;
510✔
1740
    }
1741

1742
    private createMultipleInstructions(instructionLine: uinteger | null, range: Range, severity: ValidationSeverity | undefined, instruction: string): Diagnostic | null {
1743
        if (severity === ValidationSeverity.ERROR) {
40✔
1744
            return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InstructionMultiple(instruction), ValidationCode.MULTIPLE_INSTRUCTIONS, [DiagnosticTag.Unnecessary]);
12✔
1745
        } else if (severity === ValidationSeverity.WARNING) {
28✔
1746
            return Validator.createWarning(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InstructionMultiple(instruction), ValidationCode.MULTIPLE_INSTRUCTIONS, [DiagnosticTag.Unnecessary]);
24✔
1747
        }
1748
        return null;
4✔
1749
    }
1750

1751
    private createLowercaseDirective(start: Position, end: Position): Diagnostic | null {
1752
        if (this.settings.directiveCasing === ValidationSeverity.ERROR) {
6✔
1753
            return Validator.createError(null, start, end, Validator.getDiagnosticMessage_DirectiveCasing(), ValidationCode.CASING_DIRECTIVE);
1✔
1754
        } else if (this.settings.directiveCasing === ValidationSeverity.WARNING) {
5✔
1755
            return Validator.createWarning(null, start, end, Validator.getDiagnosticMessage_DirectiveCasing(), ValidationCode.CASING_DIRECTIVE);
4✔
1756
        }
1757
        return null;
1✔
1758
    }
1759

1760
    createUppercaseInstruction(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic | null {
1761
        if (this.settings.instructionCasing === ValidationSeverity.ERROR) {
85✔
1762
            return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionCasing(), ValidationCode.CASING_INSTRUCTION);
2✔
1763
        } else if (this.settings.instructionCasing === ValidationSeverity.WARNING) {
83✔
1764
            return Validator.createWarning(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionCasing(), ValidationCode.CASING_INSTRUCTION);
78✔
1765
        }
1766
        return null;
5✔
1767
    }
1768

1769
    createMaintainerDeprecated(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic | null {
1770
        if (this.settings.deprecatedMaintainer === ValidationSeverity.ERROR) {
82✔
1771
            return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_DeprecatedMaintainer(), ValidationCode.DEPRECATED_MAINTAINER, [DiagnosticTag.Deprecated]);
2✔
1772
        } else if (this.settings.deprecatedMaintainer === ValidationSeverity.WARNING) {
80✔
1773
            return Validator.createWarning(instructionLine, start, end, Validator.getDiagnosticMessage_DeprecatedMaintainer(), ValidationCode.DEPRECATED_MAINTAINER, [DiagnosticTag.Deprecated]);
4✔
1774
        }
1775
        return null;
76✔
1776
    }
1777

1778
    private createWORKDIRNotAbsolute(instructionLine: uinteger | null,range: Range): Diagnostic | null {
1779
        if (this.settings.instructionWorkdirRelative === ValidationSeverity.ERROR) {
32✔
1780
            return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_WORKDIRPathNotAbsolute(), ValidationCode.WORKDIR_IS_NOT_ABSOLUTE);
8✔
1781
        } else if (this.settings.instructionWorkdirRelative === ValidationSeverity.WARNING) {
24✔
1782
            return Validator.createWarning(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_WORKDIRPathNotAbsolute(), ValidationCode.WORKDIR_IS_NOT_ABSOLUTE);
16✔
1783
        }
1784
        return null;
8✔
1785
    }
1786

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

1791
    static createDiagnostic(severity: DiagnosticSeverity, instructionLine: uinteger | null, start: Position, end: Position, description: string, code?: ValidationCode, tags?: DiagnosticTag[]): DockerfileDiagnostic {
1792
        return {
1,491✔
1793
            instructionLine: instructionLine,
1794
            range: {
1795
                start: start,
1796
                end: end
1797
            },
1798
            message: description,
1799
            severity: severity,
1800
            code: code,
1801
            tags: tags,
1802
            source: "dockerfile-utils"
1803
        };
1804
    }
1805
}
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