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

rcjsuen / dockerfile-utils / 6130763627

09 Sep 2023 12:06PM UTC coverage: 99.104% (+0.009%) from 99.095%
6130763627

push

github

rcjsuen
Synchronize supported Docker CE versions

The version in the CLI and the documentation does not match right now.
We should at least synchronize it up for now even if we are still way
off the mark. We will try to verify the latest version that we support
after updating the library with the recent changes in Docker CE.

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

601 of 611 branches covered (0.0%)

Branch coverage included in aggregate %.

1059 of 1064 relevant lines covered (99.53%)

771.78 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,697✔
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,697✔
59
            this.settings = settings;
5,643✔
60
        }
61
    }
62

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

71
        if (duplicatedEscapes.length > 1) {
5,701✔
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,700✔
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,260✔
116
        if (args.length === 0) {
10,260✔
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,604✔
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,909✔
132
                if (expectedArgCount[i] === args.length) {
5,958✔
133
                    for (let j = 0; j < args.length; j++) {
5,887✔
134
                        let range = args[j].getRange();
5,957✔
135
                        let createInvalidDiagnostic = validate(j, args[j].getValue(), range);
5,957✔
136
                        if (createInvalidDiagnostic instanceof Function) {
5,957✔
137
                            problems.push(createInvalidDiagnostic(instruction.getInstructionRange().start.line, range.start, range.end, args[j].getValue()));
5✔
138
                        } else if (createInvalidDiagnostic !== null) {
5,952✔
139
                            problems.push(createInvalidDiagnostic);
46✔
140
                        }
141
                    }
142
                    return;
5,887✔
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,476✔
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,701✔
231
        let problems: DockerfileDiagnostic[] = [];
5,701✔
232
        let dockerfile = DockerfileParser.parse(document.getText());
5,701✔
233
        this.checkDirectives(dockerfile, problems);
5,701✔
234
        let instructions = dockerfile.getInstructions();
5,701✔
235
        if (instructions.length === 0 || dockerfile.getARGs().length === instructions.length) {
5,701✔
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,701✔
241
        let entrypoints: Entrypoint[] = [];
5,701✔
242
        let healthchecks: Healthcheck[] = [];
5,701✔
243
        for (let instruction of instructions) {
5,701✔
244
            if (instruction instanceof Cmd) {
11,476✔
245
                cmds.push(instruction);
112✔
246
            } else if (instruction instanceof Entrypoint) {
11,364✔
247
                entrypoints.push(instruction);
97✔
248
            } else if (instruction instanceof Healthcheck) {
11,267✔
249
                healthchecks.push(instruction);
259✔
250
            } else if (instruction instanceof From) {
11,008✔
251
                this.createDuplicatesDiagnostics(problems, this.settings.instructionCmdMultiple, "CMD", cmds);
5,792✔
252
                this.createDuplicatesDiagnostics(problems, this.settings.instructionEntrypointMultiple, "ENTRYPOINT", entrypoints);
5,792✔
253
                this.createDuplicatesDiagnostics(problems, this.settings.instructionHealthcheckMultiple, "HEALTHCHECK", healthchecks);
5,792✔
254

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

265
        let escapeChar = dockerfile.getEscapeCharacter();
5,701✔
266
        let hasFrom = false;
5,701✔
267
        for (let instruction of dockerfile.getInstructions()) {
5,701✔
268
            let keyword = instruction.getKeyword();
11,476✔
269
            if (keyword === "FROM") {
11,476✔
270
                hasFrom = true;
5,792✔
271
            } else if (!hasFrom && keyword !== "ARG") {
5,684✔
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,476✔
278
            this.checkVariables(instruction, problems);
11,476✔
279
        }
280

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

285
        const ignoredLines = [];
5,701✔
286
        for (const comment of dockerfile.getComments()) {
5,701✔
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,701✔
293
            if (problems[i].instructionLine !== null) {
1,484✔
294
                for (const ignoredLine of ignoredLines) {
1,444✔
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,701✔
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,574✔
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,552✔
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,552✔
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,552✔
355
            if (fullRange.start.line !== fullRange.end.line && !isTrigger) {
11,552✔
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,552✔
392
                case "CMD":
13,039✔
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,797✔
427
                    const fromFlags = (instruction as ModifiableInstruction).getFlags();
5,797✔
428
                    for (const flag of fromFlags) {
5,797✔
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,797✔
436
                    this.checkArguments(instruction, problems, [1, 3], function (index: number, argument: string, range: Range): Diagnostic | Function | null {
5,797✔
437
                        switch (index) {
5,803✔
438
                            case 0:
5,803!
439
                                let variables = instruction.getVariables();
5,733✔
440
                                if (variables.length > 0) {
5,733✔
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,713✔
453
                                let digestRange = from.getImageDigestRange();
5,713✔
454
                                if (digestRange === null) {
5,713✔
455
                                    let tagRange = from.getImageTagRange();
5,671✔
456
                                    if (tagRange === null) {
5,671✔
457
                                        return null;
5,638✔
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
                                let digest = document.getText(digestRange);
42✔
471
                                let algorithmIndex = digest.indexOf(':');
42✔
472
                                if (algorithmIndex === -1) {
42✔
473
                                    if (digest === "") {
21✔
474
                                        // no digest specified, just highlight the whole argument
475
                                        return Validator.createInvalidReferenceFormat(fromInstructionRange.start.line, range);
7✔
476
                                    }
477
                                    return Validator.createInvalidReferenceFormat(fromInstructionRange.start.line, from.getImageDigestRange());
14✔
478
                                }
479
                                let algorithmRegexp = new RegExp(/[A-Fa-f0-9_+.-]+/);
21✔
480
                                let algorithm = digest.substring(0, algorithmIndex);
21✔
481
                                if (!algorithmRegexp.test(algorithm)) {
21✔
482
                                    return Validator.createInvalidReferenceFormat(fromInstructionRange.start.line, from.getImageDigestRange());
7✔
483
                                }
484
                                let hex = digest.substring(algorithmIndex + 1);
14✔
485
                                let hexRegexp = new RegExp(/[A-Fa-f0-9]+/);
14✔
486
                                if (hexRegexp.test(hex)) {
14✔
487
                                    return null;
7✔
488
                                }
489
                                return Validator.createInvalidReferenceFormat(fromInstructionRange.start.line, from.getImageDigestRange());
7✔
490
                            case 1:
491
                                return argument.toUpperCase() === "AS" ? null : Validator.createInvalidAs;
35✔
492
                            case 2:
493
                                argument = argument.toLowerCase();
35✔
494
                                let regexp = new RegExp(/^[a-z]([a-z0-9_\-.]*)*$/);
35✔
495
                                if (regexp.test(argument)) {
35✔
496
                                    return null;
32✔
497
                                }
498
                                return Validator.createInvalidBuildStageName(fromInstructionRange.start.line, range, argument);;
3✔
499
                            default:
500
                                return null;
×
501
                        }
502
                    }, Validator.createRequiresOneOrThreeArguments);
503
                    break;
5,797✔
504
                case "HEALTHCHECK":
505
                    let args = instruction.getArguments();
273✔
506
                    const healthcheckInstructionRange = instruction.getInstructionRange();
273✔
507
                    const healthcheckFlags = (instruction as ModifiableInstruction).getFlags();
273✔
508
                    if (args.length === 0) {
273✔
509
                        // all instructions are expected to have at least one argument
510
                        problems.push(Validator.createHEALTHCHECKRequiresAtLeastOneArgument(healthcheckInstructionRange.start.line, healthcheckInstructionRange));
53✔
511
                    } else {
512
                        const value = args[0].getValue();
220✔
513
                        const uppercase = value.toUpperCase();
220✔
514
                        if (uppercase === "NONE") {
220✔
515
                            // check that NONE doesn't have any arguments after it
516
                            if (args.length > 1) {
34✔
517
                                // get the next argument
518
                                const start = args[1].getRange().start;
2✔
519
                                // get the last argument
520
                                const end = args[args.length - 1].getRange().end;
2✔
521
                                // highlight everything after the NONE and warn the user
522
                                problems.push(Validator.createHealthcheckNoneUnnecessaryArgument(healthcheckInstructionRange.start.line, start, end));
2✔
523
                            }
524
                            // don't need to validate flags of a NONE
525
                            break;
34✔
526
                        } else if (uppercase === "CMD") {
186✔
527
                            if (args.length === 1) {
181✔
528
                                // this HEALTHCHECK has a CMD with no arguments
529
                                const range = args[0].getRange();
3✔
530
                                problems.push(Validator.createHealthcheckCmdArgumentMissing(healthcheckInstructionRange.start.line, range.start, range.end));
3✔
531
                            }
532
                        } else {
533
                            // unknown HEALTHCHECK type
534
                            problems.push(Validator.createHealthcheckTypeUnknown(healthcheckInstructionRange.start.line, args[0].getRange(), uppercase));
5✔
535
                        }
536
                    }
537

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

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

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

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

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

763
    private hasHeredocs(args: Argument[]): boolean {
764
        for (const arg of args) {
62✔
765
            if (arg.getValue().startsWith("<<")) {
142✔
766
                return true;
28✔
767
            }
768
        }
769
        return false;
34✔
770
    }
771

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

789
    private checkDestinationIsDirectory(instruction: JSONInstruction, requiresTwoArgumentsFunction: (instructionLine: uinteger, range: Range) => Diagnostic, notDirectoryFunction: (instructionLine: uinteger, range: Range) => Diagnostic): Diagnostic | null {
790
        if (instruction.getClosingBracket()) {
308✔
791
            return this.checkJsonDestinationIsDirectory(instruction, requiresTwoArgumentsFunction, notDirectoryFunction);
59✔
792
        }
793

794
        const args = instruction.getArguments();
249✔
795
        if (args.length === 1) {
249✔
796
            return requiresTwoArgumentsFunction(instruction.getInstructionRange().start.line, args[0].getRange());
4✔
797
        } else if (args.length === 0) {
245✔
798
            const instructionRange = instruction.getInstructionRange();
104✔
799
            return requiresTwoArgumentsFunction(instructionRange.start.line, instructionRange);
104✔
800
        } else if (args.length > 2) {
141✔
801
            const lastArg = this.getDestinationArgument(args);
33✔
802
            if (lastArg === null) {
33✔
803
                const instructionRange = instruction.getInstructionRange();
4✔
804
                return requiresTwoArgumentsFunction(instructionRange.start.line, instructionRange);
4✔
805
            } else if (this.hasHeredocs(args)) {
29✔
806
                return null;
12✔
807
            }
808
            const variables = instruction.getVariables();
17✔
809
            if (variables.length !== 0) {
17✔
810
                const lastJsonStringOffset = this.document.offsetAt(lastArg.getRange().end);
4✔
811
                const lastVarOffset = this.document.offsetAt(variables[variables.length - 1].getRange().end);
4✔
812
                if (lastJsonStringOffset === lastVarOffset || lastJsonStringOffset -1 === lastVarOffset) {
4!
813
                    return null;
4✔
814
                }
815
            }
816
            const destination = lastArg.getValue();
13✔
817
            const lastChar = destination.charAt(destination.length - 1);
13✔
818
            if (lastChar !== '\\' && lastChar !== '/') {
13✔
819
                return notDirectoryFunction(instruction.getInstructionRange().start.line, lastArg.getRange());
8✔
820
            }
821
        }
822
        return null;
113✔
823
    }
824

825
    private createDuplicatesDiagnostics(problems: Diagnostic[], severity: ValidationSeverity, instruction: string, instructions: Instruction[]): void {
826
        if (instructions.length > 1) {
34,479✔
827
            // decrement length by 1 because we want to ignore the last one
828
            for (let i = 0; i < instructions.length - 1; i++) {
40✔
829
                const instructionRange = instructions[i].getInstructionRange();
40✔
830
                const diagnostic = this.createMultipleInstructions(instructionRange.start.line, instructionRange, severity, instruction);
40✔
831
                if (diagnostic) {
40✔
832
                    problems.push(diagnostic);
36✔
833
                }
834
            }
835
        }
836
    }
837

838
    private createDuplicateBuildStageNameDiagnostics(problems: Diagnostic[], froms: From[]): void {
839
        const names: any = {};
5,701✔
840
        for (let from of froms) {
5,701✔
841
            let name = from.getBuildStage();
5,792✔
842
            if (name !== null) {
5,792✔
843
                name = name.toLowerCase();
36✔
844
                if (names[name] === undefined) {
36✔
845
                    names[name] = [from];
32✔
846
                } else {
847
                    names[name].push(from);
4✔
848
                }
849
            }
850
        }
851

852
        for (const name in names) {
5,701✔
853
            // duplicates found
854
            if (names[name].length > 1) {
32✔
855
                for (const from of names[name]) {
4✔
856
                    problems.push(Validator.createDuplicateBuildStageName(from.getInstructionRange().start.line, from.getBuildStageRange(), name));
8✔
857
                }
858
            }
859
        }
860
    }
861

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

888
    private checkFlagValue(instructionLine: uinteger, flags: Flag[], validFlagNames: string[], problems: Diagnostic[]): void {
889
        for (let flag of flags) {
6,344✔
890
            let flagName = flag.getName();
210✔
891
            // only validate flags with the right name
892
            if (flag.getValue() === null && validFlagNames.indexOf(flagName) !== -1) {
210✔
893
                problems.push(Validator.createFlagMissingValue(instructionLine, flag.getNameRange(), flagName));
11✔
894
            }
895
        }
896
    }
897

898
    /**
899
     * Checks that the given boolean flag is valid. A boolean flag should
900
     * either have no value defined (--flag) or the value should
901
     * case-insensitively be either "true" or "false" (--flag==tRUe is
902
     * valid).
903
     */
904
    private checkFlagBoolean(instructionLine: uinteger, flag: Flag): Diagnostic | null {
905
        const linkValue = flag.getValue();
18✔
906
        if (linkValue === "") {
18✔
907
            return Validator.createFlagMissingValue(instructionLine, flag.getNameRange(), flag.getName());
2✔
908
        } else if (linkValue !== null) {
16✔
909
            const convertedLinkValue = linkValue.toLowerCase();
14✔
910
            if (convertedLinkValue !== "true" && convertedLinkValue !== "false") {
14✔
911
                return Validator.createFlagInvalidLink(instructionLine, flag.getValueRange(), linkValue);
2✔
912
            }
913
        }
914
        return null;
14✔
915
    }
916

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

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

1094
                    if (!durationSpecified) {
60✔
1095
                        let range = flag.getValueRange();
9✔
1096
                        problems.push(Validator.createFlagMissingDuration(instructionLine, range.start, range.end, value));
9✔
1097
                    } else if (duration < 1) {
51✔
1098
                        let range = flag.getNameRange();
15✔
1099
                        problems.push(Validator.createFlagLessThan1ms(instructionLine, range.start, range.end, flagName));
15✔
1100
                    }
1101
                }
1102
            }
1103
        }
1104
    }
1105

1106
    private static isNumberRelated(character: string) {
1107
        switch (character) {
70✔
1108
            case '.':
270✔
1109
            case '0':
1110
            case '1':
1111
            case '2':
1112
            case '3':
1113
            case '4':
1114
            case '5':
1115
            case '6':
1116
            case '7':
1117
            case '8':
1118
            case '9':
1119
                return true;
36✔
1120
        }
1121
        return false;
34✔
1122
    }
1123

1124
    private checkDuplicateFlags(instructionLine: uinteger, flags: Flag[], validFlags: string[], problems: Diagnostic[]): void {
1125
        let flagNames = flags.map(function (flag) {
547✔
1126
            return flag.getName();
204✔
1127
        });
1128
        for (let validFlag of validFlags) {
547✔
1129
            let index = flagNames.indexOf(validFlag);
2,282✔
1130
            let lastIndex = flagNames.lastIndexOf(validFlag);
2,282✔
1131
            if (index !== lastIndex) {
2,282✔
1132
                let range = flags[index].getNameRange();
8✔
1133
                problems.push(Validator.createFlagDuplicate(instructionLine, range.start, range.end, flagNames[index]));
8✔
1134
                range = flags[lastIndex].getNameRange();
8✔
1135
                problems.push(Validator.createFlagDuplicate(instructionLine, range.start, range.end, flagNames[index]));
8✔
1136
            }
1137
        }
1138
    }
1139

1140
    private checkJSON(document: TextDocument, instruction: JSONInstruction, problems: Diagnostic[]) {
1141
        let argsContent = instruction.getArgumentsContent();
110✔
1142
        if (argsContent === null) {
110✔
1143
            return;
50✔
1144
        }
1145

1146
        let argsRange = instruction.getArgumentsRange();
60✔
1147
        let args = instruction.getArguments();
60✔
1148
        if ((args.length === 1 && args[0].getValue() === "[]") ||
60✔
1149
            (args.length === 2 && args[0].getValue() === '[' && args[1].getValue() === ']')) {
1150
            problems.push(Validator.createShellRequiresOne(instruction.getInstructionRange().start.line, argsRange));
3✔
1151
            return;
3✔
1152
        }
1153

1154
        const closing = instruction.getClosingBracket();
57✔
1155
        if (closing) {
57✔
1156
            let content = document.getText();
45✔
1157
            content = content.substring(
45✔
1158
                document.offsetAt(instruction.getOpeningBracket().getRange().end),
1159
                document.offsetAt(closing.getRange().start)
1160
            );
1161
            content = content.trim();
45✔
1162
            if (content.charAt(content.length - 1) !== '"') {
45✔
1163
                problems.push(Validator.createShellJsonForm(instruction.getInstructionRange().start.line, argsRange));
1✔
1164
            }
1165
        } else {
1166
            problems.push(Validator.createShellJsonForm(instruction.getInstructionRange().start.line, argsRange));
12✔
1167
        }
1168
    }
1169

1170
    private checkJSONQuotes(instruction: Instruction, problems: Diagnostic[]) {
1171
        let argsContent = instruction.getArgumentsContent();
761✔
1172
        if (argsContent === null) {
761✔
1173
            return;
308✔
1174
        }
1175

1176
        let argsRange = instruction.getArgumentsRange();
453✔
1177
        let args = instruction.getArguments();
453✔
1178
        if ((args.length === 1 && args[0].getValue() === "[]") ||
453✔
1179
            (args.length === 2 && args[0].getValue() === '[' && args[1].getValue() === ']')) {
1180
            return;
4✔
1181
        }
1182

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

1246
    private static dockerProblems = {
1✔
1247
        "baseNameEmpty": "base name (${0}) should not be blank",
1248

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

1253
        "noSourceImage": "No source image provided with `FROM`",
1254

1255
        "emptyContinuationLine": "Empty continuation line",
1256

1257
        "fromRequiresOneOrThreeArguments": "FROM requires either one or three arguments",
1258

1259
        "invalidAs": "Second argument should be AS",
1260
        "invalidPort": "Invalid containerPort: ${0}",
1261
        "invalidProtocol": "Invalid proto: ${0}",
1262
        "invalidReferenceFormat": "invalid reference format",
1263
        "invalidStopSignal": "Invalid signal: ${0}",
1264
        "invalidSyntax": "parsing \"${0}\": invalid syntax",
1265
        "invalidDestination": "When using ${0} with more than one source file, the destination must be a directory and end with a / or a \\",
1266

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

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

1275
        "flagAtLeastOne": "${0} must be at least 1 (not ${1})",
1276
        "flagDuplicate": "Duplicate flag specified: ${0}",
1277
        "flagInvalidDuration": "time: invalid duration ${0}",
1278
        "flagInvalidFrom": "invalid from flag value ${0}: invalid reference format",
1279
        "flagInvalidLink": "expecting boolean value for flag link, not: ${0}",
1280
        "flagLessThan1ms": "Interval \"${0}\" cannot be less than 1ms",
1281
        "flagMissingDuration": "time: missing unit in duration ${0}",
1282
        "flagMissingValue": "Missing a value on flag: ${0}",
1283
        "flagUnknown": "Unknown flag: ${0}",
1284
        "flagUnknownUnit": "time: unknown unit ${0} in duration ${1}",
1285

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

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

1300
        "onbuildChainingDisallowed": "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed",
1301
        "onbuildTriggerDisallowed": "${0} isn't allowed as an ONBUILD trigger",
1302

1303
        "shellJsonForm": "SHELL requires the arguments to be in JSON form",
1304
        "shellRequiresOne": "SHELL requires at least one argument",
1305

1306
        "deprecatedMaintainer": "MAINTAINER has been deprecated",
1307

1308
        "healthcheckCmdArgumentMissing": "Missing command after HEALTHCHECK CMD",
1309
        "healthcheckTypeUnknown": "Unknown type\"${0}\" in HEALTHCHECK (try CMD)",
1310

1311
        "workdirPathNotAbsolute": "WORKDIR paths should be absolute"
1312
    };
1313

1314
    private static formatMessage(text: string, ...variables: string[]): string {
1315
        for (let i = 0; i < variables.length; i++) {
1,181✔
1316
            text = text.replace("${" + i + "}", variables[i]);
1,257✔
1317
        }
1318
        return text;
1,181✔
1319
    }
1320

1321
    public static getDiagnosticMessage_DirectiveCasing() {
1322
        return Validator.dockerProblems["directiveCasing"];
10✔
1323
    }
1324

1325
    public static getDiagnosticMessage_DirectiveEscapeDuplicated() {
1326
        return Validator.dockerProblems["directiveEscapeDuplicated"];
2✔
1327
    }
1328

1329
    public static getDiagnosticMessage_DirectiveEscapeInvalid(value: string) {
1330
        return Validator.formatMessage(Validator.dockerProblems["directiveEscapeInvalid"], value);
8✔
1331
    }
1332

1333
    public static getDiagnosticMessage_NoSourceImage() {
1334
        return Validator.dockerProblems["noSourceImage"];
34✔
1335
    }
1336

1337
    public static getDiagnosticMessage_EmptyContinuationLine() {
1338
        return Validator.dockerProblems["emptyContinuationLine"];
26✔
1339
    }
1340

1341
    public static getDiagnosticMessage_DuplicateBuildStageName(name: string) {
1342
        return Validator.formatMessage(Validator.dockerProblems["duplicateBuildStageName"], name);
16✔
1343
    }
1344

1345
    public static getDiagnosticMessage_InvalidBuildStageName(name: string) {
1346
        return Validator.formatMessage(Validator.dockerProblems["invalidBuildStageName"], name);
6✔
1347
    }
1348

1349
    public static getDiagnosticMessage_FlagAtLeastOne(flagName: string, flagValue: string) {
1350
        return Validator.formatMessage(Validator.dockerProblems["flagAtLeastOne"], flagName, flagValue);
4✔
1351
    }
1352

1353
    public static getDiagnosticMessage_FlagDuplicate(flag: string) {
1354
        return Validator.formatMessage(Validator.dockerProblems["flagDuplicate"], flag);
32✔
1355
    }
1356

1357
    public static getDiagnosticMessage_FlagInvalidDuration(flag: string) {
1358
        return Validator.formatMessage(Validator.dockerProblems["flagInvalidDuration"], flag);
18✔
1359
    }
1360

1361
    public static getDiagnosticMessage_FlagLessThan1ms(flag: string) {
1362
        return Validator.formatMessage(Validator.dockerProblems["flagLessThan1ms"], flag);
54✔
1363
    }
1364

1365
    public static getDiagnosticMessage_FlagMissingDuration(duration: string) {
1366
        return Validator.formatMessage(Validator.dockerProblems["flagMissingDuration"], duration);
36✔
1367
    }
1368

1369
    public static getDiagnosticMessage_FlagInvalidFromValue(value: string): string {
1370
        return Validator.formatMessage(Validator.dockerProblems["flagInvalidFrom"], value);
6✔
1371
    }
1372

1373
    public static getDiagnosticMessage_FlagInvalidLinkValue(value: string): string {
1374
        return Validator.formatMessage(Validator.dockerProblems["flagInvalidLink"], value);
4✔
1375
    }
1376

1377
    public static getDiagnosticMessage_FlagMissingValue(flag: string) {
1378
        return Validator.formatMessage(Validator.dockerProblems["flagMissingValue"], flag);
26✔
1379
    }
1380

1381
    public static getDiagnosticMessage_FlagUnknown(flag: string) {
1382
        return Validator.formatMessage(Validator.dockerProblems["flagUnknown"], flag);
62✔
1383
    }
1384

1385
    public static getDiagnosticMessage_FlagUnknownUnit(unit: string, duration: string) {
1386
        return Validator.formatMessage(Validator.dockerProblems["flagUnknownUnit"], unit, duration);
30✔
1387
    }
1388

1389
    public static getDiagnosticMessage_BaseNameEmpty(name: string) {
1390
        return Validator.formatMessage(Validator.dockerProblems["baseNameEmpty"], name);
4✔
1391
    }
1392

1393
    public static getDiagnosticMessage_InvalidAs() {
1394
        return Validator.dockerProblems["invalidAs"];
2✔
1395
    }
1396

1397
    public static getDiagnosticMessage_InvalidPort(port: string) {
1398
        return Validator.formatMessage(Validator.dockerProblems["invalidPort"], port);
54✔
1399
    }
1400

1401
    public static getDiagnosticMessage_InvalidProto(protocol: string) {
1402
        return Validator.formatMessage(Validator.dockerProblems["invalidProtocol"], protocol);
8✔
1403
    }
1404

1405
    public static getDiagnosticMessage_InvalidReferenceFormat() {
1406
        return Validator.dockerProblems["invalidReferenceFormat"];
82✔
1407
    }
1408

1409
    public static getDiagnosticMessage_InvalidSignal(signal: string) {
1410
        return Validator.formatMessage(Validator.dockerProblems["invalidStopSignal"], signal);
14✔
1411
    }
1412

1413
    public static getDiagnosticMessage_InvalidSyntax(syntax: string) {
1414
        return Validator.formatMessage(Validator.dockerProblems["invalidSyntax"], syntax);
4✔
1415
    }
1416

1417
    public static getDiagnosticMessage_InstructionExtraArgument() {
1418
        return Validator.dockerProblems["instructionExtraArgument"];
16✔
1419
    }
1420

1421
    public static getDiagnosticMessage_InstructionMissingArgument() {
1422
        return Validator.dockerProblems["instructionMissingArgument"];
1,304✔
1423
    }
1424

1425
    public static getDiagnosticMessage_ADDDestinationNotDirectory() {
1426
        return Validator.formatMessage(Validator.dockerProblems["invalidDestination"], "ADD");
16✔
1427
    }
1428

1429
    public static getDiagnosticMessage_ADDRequiresAtLeastTwoArguments() {
1430
        return Validator.formatMessage(Validator.dockerProblems["instructionRequiresAtLeastTwoArguments"], "ADD");
122✔
1431
    }
1432

1433
    public static getDiagnosticMessage_COPYDestinationNotDirectory() {
1434
        return Validator.formatMessage(Validator.dockerProblems["invalidDestination"], "COPY");
26✔
1435
    }
1436

1437
    public static getDiagnosticMessage_COPYRequiresAtLeastTwoArguments() {
1438
        return Validator.formatMessage(Validator.dockerProblems["instructionRequiresAtLeastTwoArguments"], "COPY");
122✔
1439
    }
1440

1441
    public static getDiagnosticMessage_HEALTHCHECKRequiresAtLeastOneArgument() {
1442
        return Validator.formatMessage(Validator.dockerProblems["instructionRequiresAtLeastOneArgument"], "HEALTHCHECK");
106✔
1443
    }
1444

1445
    public static getDiagnosticMessage_ENVRequiresTwoArguments() {
1446
        return Validator.formatMessage(Validator.dockerProblems["instructionRequiresTwoArguments"], "ENV");
4✔
1447
    }
1448

1449
    public static getDiagnosticMessage_InstructionRequiresOneOrThreeArguments() {
1450
        return Validator.dockerProblems["fromRequiresOneOrThreeArguments"];
28✔
1451
    }
1452

1453
    public static getDiagnosticMessage_HealthcheckNoneUnnecessaryArgument() {
1454
        return Validator.formatMessage(Validator.dockerProblems["instructionUnnecessaryArgument"], "HEALTHCHECK NONE");
4✔
1455
    }
1456

1457
    public static getDiagnosticMessage_InstructionMultiple(instruction: string) {
1458
        return Validator.formatMessage(Validator.dockerProblems["instructionMultiple"], instruction);
72✔
1459
    }
1460

1461
    public static getDiagnosticMessage_InstructionUnknown(instruction: string) {
1462
        return Validator.formatMessage(Validator.dockerProblems["instructionUnknown"], instruction);
41✔
1463
    }
1464

1465
    public static getDiagnosticMessage_SyntaxMissingEquals(argument: string) {
1466
        return Validator.formatMessage(Validator.dockerProblems["syntaxMissingEquals"], argument);
8✔
1467
    }
1468

1469
    public static getDiagnosticMessage_SyntaxMissingNames(instruction: string) {
1470
        return Validator.formatMessage(Validator.dockerProblems["syntaxMissingNames"], instruction);
20✔
1471
    }
1472

1473
    public static getDiagnosticMessage_SyntaxMissingSingleQuote(key: string) {
1474
        return Validator.formatMessage(Validator.dockerProblems["syntaxMissingSingleQuote"], key);
24✔
1475
    }
1476

1477
    public static getDiagnosticMessage_SyntaxMissingDoubleQuote(key: string) {
1478
        return Validator.formatMessage(Validator.dockerProblems["syntaxMissingDoubleQuote"], key);
54✔
1479
    }
1480

1481
    public static getDiagnosticMessage_InstructionCasing() {
1482
        return Validator.dockerProblems["instructionCasing"];
160✔
1483
    }
1484

1485
    public static getDiagnosticMessage_InstructionJSONInSingleQuotes() {
1486
        return Validator.dockerProblems["instructionJSONInSingleQuotes"];
60✔
1487
    }
1488

1489
    public static getDiagnosticMessage_OnbuildChainingDisallowed() {
1490
        return Validator.dockerProblems["onbuildChainingDisallowed"];
10✔
1491
    }
1492

1493
    public static getDiagnosticMessage_OnbuildTriggerDisallowed(trigger: string) {
1494
        return Validator.formatMessage(Validator.dockerProblems["onbuildTriggerDisallowed"], trigger);
28✔
1495
    }
1496

1497
    public static getDiagnosticMessage_VariableModifierUnsupported(variable: string, modifier: string) {
1498
        return Validator.formatMessage(Validator.dockerProblems["variableModifierUnsupported"], variable, modifier);
90✔
1499
    }
1500

1501
    public static getDiagnosticMessage_ShellJsonForm() {
1502
        return Validator.dockerProblems["shellJsonForm"];
26✔
1503
    }
1504

1505
    public static getDiagnosticMessage_ShellRequiresOne() {
1506
        return Validator.dockerProblems["shellRequiresOne"];
6✔
1507
    }
1508

1509
    public static getDiagnosticMessage_DeprecatedMaintainer() {
1510
        return Validator.dockerProblems["deprecatedMaintainer"];
12✔
1511
    }
1512

1513
    public static getDiagnosticMessage_HealthcheckCmdArgumentMissing() {
1514
        return Validator.dockerProblems["healthcheckCmdArgumentMissing"];
6✔
1515
    }
1516

1517
    public static getDiagnosticMessage_HealthcheckTypeUnknown(type: string) {
1518
        return Validator.formatMessage(Validator.dockerProblems["healthcheckTypeUnknown"], type);
10✔
1519
    }
1520

1521
    public static getDiagnosticMessage_WORKDIRPathNotAbsolute() {
1522
        return Validator.formatMessage(Validator.dockerProblems["workdirPathNotAbsolute"]);
48✔
1523
    }
1524

1525
    private static createDuplicatedEscapeDirective(start: Position, end: Position): Diagnostic {
1526
        return Validator.createError(null, start, end, Validator.getDiagnosticMessage_DirectiveEscapeDuplicated(), ValidationCode.DUPLICATED_ESCAPE_DIRECTIVE, [DiagnosticTag.Unnecessary]);
1✔
1527
    }
1528

1529
    private static createInvalidEscapeDirective(start: Position, end: Position, value: string): Diagnostic {
1530
        return Validator.createError(null, start, end, Validator.getDiagnosticMessage_DirectiveEscapeInvalid(value), ValidationCode.INVALID_ESCAPE_DIRECTIVE);
4✔
1531
    }
1532

1533
    private static createDuplicateBuildStageName(instructionLine: uinteger, range: Range, name: string): Diagnostic {
1534
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_DuplicateBuildStageName(name), ValidationCode.DUPLICATE_BUILD_STAGE_NAME);
8✔
1535
    }
1536

1537
    private static createInvalidBuildStageName(instructionLine: uinteger | null, range: Range, name: string): Diagnostic {
1538
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InvalidBuildStageName(name), ValidationCode.INVALID_BUILD_STAGE_NAME);
3✔
1539
    }
1540

1541
    private static createFlagAtLeastOne(instructionLine: uinteger | null, start: Position, end: Position, flagName: string, flagValue: string): Diagnostic {
1542
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagAtLeastOne(flagName, flagValue), ValidationCode.FLAG_AT_LEAST_ONE);
2✔
1543
    }
1544

1545
    private static createFlagDuplicate(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1546
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagDuplicate(flag), ValidationCode.FLAG_DUPLICATE);
16✔
1547
    }
1548

1549
    private static createFlagInvalidDuration(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1550
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagInvalidDuration(flag), ValidationCode.FLAG_INVALID_DURATION);
9✔
1551
    }
1552

1553
    private static createFlagLessThan1ms(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1554
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagLessThan1ms(flag), ValidationCode.FLAG_LESS_THAN_1MS);
27✔
1555
    }
1556

1557
    private static createFlagMissingDuration(instructionLine: uinteger | null, start: Position, end: Position, duration: string): Diagnostic {
1558
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagMissingDuration(duration), ValidationCode.FLAG_MISSING_DURATION);
18✔
1559
    }
1560

1561
    private static createFlagInvalidFrom(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1562
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagInvalidFromValue(flag), ValidationCode.FLAG_INVALID_FROM_VALUE);
3✔
1563
    }
1564

1565
    private static createFlagInvalidLink(instructionLine: uinteger | null, range: Range, value: string): Diagnostic {
1566
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_FlagInvalidLinkValue(value), ValidationCode.FLAG_INVALID_LINK_VALUE);
2✔
1567
    }
1568

1569
    private static createFlagMissingValue(instructionLine: uinteger | null, range: Range, flag: string): Diagnostic {
1570
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_FlagMissingValue(flag), ValidationCode.FLAG_MISSING_VALUE);
13✔
1571
    }
1572

1573
    private static createUnknownAddFlag(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1574
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagUnknown(flag), ValidationCode.UNKNOWN_ADD_FLAG);
7✔
1575
    }
1576

1577
    private static createUnknownCopyFlag(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1578
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagUnknown(flag), ValidationCode.UNKNOWN_COPY_FLAG);
8✔
1579
    }
1580

1581
    private static createUnknownFromFlag(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1582
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagUnknown(flag), ValidationCode.UNKNOWN_FROM_FLAG);
3✔
1583
    }
1584

1585
    private static createUnknownHealthcheckFlag(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1586
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagUnknown(flag), ValidationCode.UNKNOWN_HEALTHCHECK_FLAG);
13✔
1587
    }
1588

1589
    private static createFlagUnknownUnit(instructionLine: uinteger | null, range: Range, unit: string, duration: string): Diagnostic {
1590
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_FlagUnknownUnit(unit, duration), ValidationCode.FLAG_UNKNOWN_UNIT);
15✔
1591
    }
1592

1593
    private static createBaseNameEmpty(instructionLine: uinteger | null, range: Range, name: string): Diagnostic {
1594
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_BaseNameEmpty(name), ValidationCode.BASE_NAME_EMPTY);
2✔
1595
    }
1596

1597
    private static createInvalidAs(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1598
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InvalidAs(), ValidationCode.INVALID_AS);
1✔
1599
    }
1600

1601
    private static createInvalidPort(instructionLine: uinteger | null, range: Range, port: string): Diagnostic {
1602
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InvalidPort(port), ValidationCode.INVALID_PORT);
27✔
1603
    }
1604

1605
    private static createInvalidProto(instructionLine: uinteger | null, start: Position, end: Position, protocol: string): Diagnostic {
1606
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InvalidProto(protocol), ValidationCode.INVALID_PROTO);
4✔
1607
    }
1608

1609
    private static createInvalidReferenceFormat(instructionLine: uinteger | null, range: Range): Diagnostic {
1610
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InvalidReferenceFormat(), ValidationCode.INVALID_REFERENCE_FORMAT);
41✔
1611
    }
1612

1613
    private static createInvalidStopSignal(instructionLine: uinteger | null, start: Position, end: Position, signal: string): Diagnostic {
1614
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InvalidSignal(signal), ValidationCode.INVALID_SIGNAL);
7✔
1615
    }
1616

1617
    private static createInvalidSyntax(instructionLine: uinteger | null, start: Position, end: Position, syntax: string): Diagnostic {
1618
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InvalidSyntax(syntax), ValidationCode.INVALID_SYNTAX);
2✔
1619
    }
1620

1621
    private static createMissingArgument(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1622
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionMissingArgument(), ValidationCode.ARGUMENT_MISSING);
652✔
1623
    }
1624

1625
    private static createExtraArgument(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1626
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionExtraArgument(), ValidationCode.ARGUMENT_EXTRA);
8✔
1627
    }
1628

1629
    private static createHealthcheckNoneUnnecessaryArgument(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1630
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_HealthcheckNoneUnnecessaryArgument(), ValidationCode.ARGUMENT_UNNECESSARY);
2✔
1631
    }
1632

1633
    private static createADDDestinationNotDirectory(instructionLine: uinteger | null, range: Range): Diagnostic {
1634
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_ADDDestinationNotDirectory(), ValidationCode.INVALID_DESTINATION);
8✔
1635
    }
1636

1637
    private static createADDRequiresAtLeastTwoArguments(instructionLine: uinteger | null, range: Range): Diagnostic {
1638
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_ADDRequiresAtLeastTwoArguments(), ValidationCode.ARGUMENT_REQUIRES_AT_LEAST_TWO);
61✔
1639
    }
1640

1641
    private static createCOPYDestinationNotDirectory(instructionLine: uinteger | null, range: Range): Diagnostic {
1642
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_COPYDestinationNotDirectory(), ValidationCode.INVALID_DESTINATION);
13✔
1643
    }
1644

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

1649
    private static createENVRequiresTwoArguments(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1650
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_ENVRequiresTwoArguments(), ValidationCode.ARGUMENT_REQUIRES_TWO);
2✔
1651
    }
1652

1653
    private static createHEALTHCHECKRequiresAtLeastOneArgument(instructionLine: uinteger | null, range: Range): Diagnostic {
1654
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_HEALTHCHECKRequiresAtLeastOneArgument(), ValidationCode.ARGUMENT_REQUIRES_AT_LEAST_ONE);
53✔
1655
    }
1656

1657
    private static createHealthcheckCmdArgumentMissing(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1658
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_HealthcheckCmdArgumentMissing(), ValidationCode.HEALTHCHECK_CMD_ARGUMENT_MISSING);
3✔
1659
    }
1660

1661
    private static createHealthcheckTypeUnknown(instructionLine: uinteger | null, range: Range, type: string): Diagnostic {
1662
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_HealthcheckTypeUnknown(type), ValidationCode.UNKNOWN_TYPE);
5✔
1663
    }
1664

1665
    private static createOnbuildChainingDisallowed(instructionLine: uinteger | null, range: Range): Diagnostic {
1666
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_OnbuildChainingDisallowed(), ValidationCode.ONBUILD_CHAINING_DISALLOWED);
5✔
1667
    }
1668

1669
    private static createOnbuildTriggerDisallowed(instructionLine: uinteger | null, range: Range, trigger: string): Diagnostic {
1670
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_OnbuildTriggerDisallowed(trigger), ValidationCode.ONBUILD_TRIGGER_DISALLOWED);
14✔
1671
    }
1672

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

1677
    private static createShellRequiresOne(instructionLine: uinteger | null, range: Range): Diagnostic {
1678
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_ShellRequiresOne(), ValidationCode.SHELL_REQUIRES_ONE);
3✔
1679
    }
1680

1681
    private static createRequiresOneOrThreeArguments(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1682
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionRequiresOneOrThreeArguments(), ValidationCode.ARGUMENT_REQUIRES_ONE_OR_THREE);
14✔
1683
    }
1684

1685
    private static createNoSourceImage(start: Position, end: Position): DockerfileDiagnostic {
1686
        return Validator.createError(null, start, end, Validator.getDiagnosticMessage_NoSourceImage(), ValidationCode.NO_SOURCE_IMAGE);
17✔
1687
    }
1688

1689
    private static createSyntaxMissingEquals(instructionLine: uinteger | null, start: Position, end: Position, argument: string): Diagnostic {
1690
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_SyntaxMissingEquals(argument), ValidationCode.SYNTAX_MISSING_EQUALS);
4✔
1691
    }
1692

1693
    private static createSyntaxMissingSingleQuote(instructionLine: uinteger | null, start: Position, end: Position, argument: string): Diagnostic {
1694
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_SyntaxMissingSingleQuote(argument), ValidationCode.SYNTAX_MISSING_SINGLE_QUOTE);
12✔
1695
    }
1696

1697
    private static createSyntaxMissingDoubleQuote(instructionLine: uinteger | null, start: Position, end: Position, argument: string): Diagnostic {
1698
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_SyntaxMissingDoubleQuote(argument), ValidationCode.SYNTAX_MISSING_DOUBLE_QUOTE);
27✔
1699
    }
1700

1701
    private static createSyntaxMissingNames(instructionLine: uinteger | null, start: Position, end: Position, instruction: string): Diagnostic {
1702
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_SyntaxMissingNames(instruction), ValidationCode.SYNTAX_MISSING_NAMES);
10✔
1703
    }
1704

1705
    private static createVariableUnsupportedModifier(instructionLine: uinteger | null, range: Range, variable: string, modifier: string): Diagnostic {
1706
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_VariableModifierUnsupported(variable, modifier), ValidationCode.UNSUPPORTED_MODIFIER);
45✔
1707
    }
1708

1709
    private static createUnknownInstruction(instructionLine: uinteger | null, start: Position, end: Position, instruction: string): Diagnostic {
1710
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionUnknown(instruction), ValidationCode.UNKNOWN_INSTRUCTION);
22✔
1711
    }
1712

1713
    private static createError(instructionLine: uinteger | null, start: Position, end: Position, description: string, code?: ValidationCode, tags?: DiagnosticTag[]): DockerfileDiagnostic {
1714
        return Validator.createDiagnostic(DiagnosticSeverity.Error, instructionLine, start, end, description, code, tags);
1,328✔
1715
    }
1716

1717
    private static createJSONInSingleQuotes(instructionLine: uinteger | null, range: Range, severity: ValidationSeverity | undefined): Diagnostic | null {
1718
        if (severity === ValidationSeverity.ERROR) {
36✔
1719
            return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InstructionJSONInSingleQuotes(), ValidationCode.JSON_IN_SINGLE_QUOTES);
6✔
1720
        } else if (severity === ValidationSeverity.WARNING) {
30✔
1721
            return Validator.createWarning(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InstructionJSONInSingleQuotes(), ValidationCode.JSON_IN_SINGLE_QUOTES);
24✔
1722
        }
1723
        return null;
6✔
1724
    }
1725

1726
    private static createEmptyContinuationLine(start: Position, end: Position, severity: ValidationSeverity | undefined): Diagnostic | null {
1727
        if (severity === ValidationSeverity.ERROR) {
523✔
1728
            return Validator.createError(null, start, end, Validator.getDiagnosticMessage_EmptyContinuationLine(), ValidationCode.EMPTY_CONTINUATION_LINE);
7✔
1729
        } else if (severity === ValidationSeverity.WARNING) {
516✔
1730
            return Validator.createWarning(null, start, end, Validator.getDiagnosticMessage_EmptyContinuationLine(), ValidationCode.EMPTY_CONTINUATION_LINE);
6✔
1731
        }
1732
        return null;
510✔
1733
    }
1734

1735
    private createMultipleInstructions(instructionLine: uinteger | null, range: Range, severity: ValidationSeverity | undefined, instruction: string): Diagnostic | null {
1736
        if (severity === ValidationSeverity.ERROR) {
40✔
1737
            return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InstructionMultiple(instruction), ValidationCode.MULTIPLE_INSTRUCTIONS, [DiagnosticTag.Unnecessary]);
12✔
1738
        } else if (severity === ValidationSeverity.WARNING) {
28✔
1739
            return Validator.createWarning(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InstructionMultiple(instruction), ValidationCode.MULTIPLE_INSTRUCTIONS, [DiagnosticTag.Unnecessary]);
24✔
1740
        }
1741
        return null;
4✔
1742
    }
1743

1744
    private createLowercaseDirective(start: Position, end: Position): Diagnostic | null {
1745
        if (this.settings.directiveCasing === ValidationSeverity.ERROR) {
6✔
1746
            return Validator.createError(null, start, end, Validator.getDiagnosticMessage_DirectiveCasing(), ValidationCode.CASING_DIRECTIVE);
1✔
1747
        } else if (this.settings.directiveCasing === ValidationSeverity.WARNING) {
5✔
1748
            return Validator.createWarning(null, start, end, Validator.getDiagnosticMessage_DirectiveCasing(), ValidationCode.CASING_DIRECTIVE);
4✔
1749
        }
1750
        return null;
1✔
1751
    }
1752

1753
    createUppercaseInstruction(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic | null {
1754
        if (this.settings.instructionCasing === ValidationSeverity.ERROR) {
85✔
1755
            return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionCasing(), ValidationCode.CASING_INSTRUCTION);
2✔
1756
        } else if (this.settings.instructionCasing === ValidationSeverity.WARNING) {
83✔
1757
            return Validator.createWarning(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionCasing(), ValidationCode.CASING_INSTRUCTION);
78✔
1758
        }
1759
        return null;
5✔
1760
    }
1761

1762
    createMaintainerDeprecated(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic | null {
1763
        if (this.settings.deprecatedMaintainer === ValidationSeverity.ERROR) {
82✔
1764
            return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_DeprecatedMaintainer(), ValidationCode.DEPRECATED_MAINTAINER, [DiagnosticTag.Deprecated]);
2✔
1765
        } else if (this.settings.deprecatedMaintainer === ValidationSeverity.WARNING) {
80✔
1766
            return Validator.createWarning(instructionLine, start, end, Validator.getDiagnosticMessage_DeprecatedMaintainer(), ValidationCode.DEPRECATED_MAINTAINER, [DiagnosticTag.Deprecated]);
4✔
1767
        }
1768
        return null;
76✔
1769
    }
1770

1771
    private createWORKDIRNotAbsolute(instructionLine: uinteger | null,range: Range): Diagnostic | null {
1772
        if (this.settings.instructionWorkdirRelative === ValidationSeverity.ERROR) {
32✔
1773
            return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_WORKDIRPathNotAbsolute(), ValidationCode.WORKDIR_IS_NOT_ABSOLUTE);
8✔
1774
        } else if (this.settings.instructionWorkdirRelative === ValidationSeverity.WARNING) {
24✔
1775
            return Validator.createWarning(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_WORKDIRPathNotAbsolute(), ValidationCode.WORKDIR_IS_NOT_ABSOLUTE);
16✔
1776
        }
1777
        return null;
8✔
1778
    }
1779

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

1784
    static createDiagnostic(severity: DiagnosticSeverity, instructionLine: uinteger | null, start: Position, end: Position, description: string, code?: ValidationCode, tags?: DiagnosticTag[]): DockerfileDiagnostic {
1785
        return {
1,484✔
1786
            instructionLine: instructionLine,
1787
            range: {
1788
                start: start,
1789
                end: end
1790
            },
1791
            message: description,
1792
            severity: severity,
1793
            code: code,
1794
            tags: tags,
1795
            source: "dockerfile-utils"
1796
        };
1797
    }
1798
}
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