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

rcjsuen / dockerfile-utils / 6130618513

09 Sep 2023 11:27AM UTC coverage: 99.095% (+0.005%) from 99.09%
6130618513

push

github

rcjsuen
Record instruction's declaration line in diagnostic

The line of the instruction that introduced the error will now be
recorded in the diagnostic itself. This will allow us to implement #106
by checking to see if there is a comment directly above the source line
of the diagnostic.

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

595 of 605 branches covered (0.0%)

Branch coverage included in aggregate %.

172 of 172 new or added lines in 1 file covered. (100.0%)

1048 of 1053 relevant lines covered (99.53%)

760.5 hits per line

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

99.21
/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,695✔
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,695✔
59
            this.settings = settings;
5,641✔
60
        }
61
    }
62

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

71
        if (duplicatedEscapes.length > 1) {
5,699✔
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,698✔
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,258✔
116
        if (args.length === 0) {
10,258✔
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,602✔
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,907✔
132
                if (expectedArgCount[i] === args.length) {
5,956✔
133
                    for (let j = 0; j < args.length; j++) {
5,885✔
134
                        let range = args[j].getRange();
5,955✔
135
                        let createInvalidDiagnostic = validate(j, args[j].getValue(), range);
5,955✔
136
                        if (createInvalidDiagnostic instanceof Function) {
5,955✔
137
                            problems.push(createInvalidDiagnostic(instruction.getInstructionRange().start.line, range.start, range.end, args[j].getValue()));
5✔
138
                        } else if (createInvalidDiagnostic !== null) {
5,950✔
139
                            problems.push(createInvalidDiagnostic);
46✔
140
                        }
141
                    }
142
                    return;
5,885✔
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,471✔
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,699✔
231
        let problems: Diagnostic[] = [];
5,699✔
232
        let dockerfile = DockerfileParser.parse(document.getText());
5,699✔
233
        this.checkDirectives(dockerfile, problems);
5,699✔
234
        let instructions = dockerfile.getInstructions();
5,699✔
235
        if (instructions.length === 0 || dockerfile.getARGs().length === instructions.length) {
5,699✔
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,699✔
241
        let entrypoints: Entrypoint[] = [];
5,699✔
242
        let healthchecks: Healthcheck[] = [];
5,699✔
243
        for (let instruction of instructions) {
5,699✔
244
            if (instruction instanceof Cmd) {
11,471✔
245
                cmds.push(instruction);
112✔
246
            } else if (instruction instanceof Entrypoint) {
11,359✔
247
                entrypoints.push(instruction);
97✔
248
            } else if (instruction instanceof Healthcheck) {
11,262✔
249
                healthchecks.push(instruction);
259✔
250
            } else if (instruction instanceof From) {
11,003✔
251
                this.createDuplicatesDiagnostics(problems, this.settings.instructionCmdMultiple, "CMD", cmds);
5,790✔
252
                this.createDuplicatesDiagnostics(problems, this.settings.instructionEntrypointMultiple, "ENTRYPOINT", entrypoints);
5,790✔
253
                this.createDuplicatesDiagnostics(problems, this.settings.instructionHealthcheckMultiple, "HEALTHCHECK", healthchecks);
5,790✔
254

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

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

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

287
    /**
288
     * Retrieves the line numbers that corresponds to the content of
289
     * here-documents in the given instruction. The line numbers are
290
     * zero-based.
291
     * 
292
     * @param instruction the instruction to check
293
     * @returns an array of line numbers where content of
294
     *          here-documents are defined
295
     */
296
    private getHeredocLines(instruction: Instruction): number[] {
297
        if (instruction instanceof Copy || instruction instanceof Run) {
3,043✔
298
            const lines: number[] = [];
99✔
299
            for (const heredoc of instruction.getHeredocs()) {
99✔
300
                const range = heredoc.getContentRange();
19✔
301
                if (range !== null) {
19!
302
                    for (let i = range.start.line; i <= range.end.line; i++) {
19✔
303
                        lines.push(i);
25✔
304
                    }
305
                 }
306
            }
307
            return lines;
99✔
308
        }
309
        return []
2,944✔
310
    }
311

312
    private validateInstruction(document: TextDocument, escapeChar: string, instruction: Instruction, keyword: string, isTrigger: boolean, problems: Diagnostic[]): void {
313
        if (KEYWORDS.indexOf(keyword) === -1) {
11,569✔
314
            let range = instruction.getInstructionRange();
19✔
315
            // invalid instruction found
316
            problems.push(Validator.createUnknownInstruction(range.start.line, range.start, range.end, keyword));
19✔
317
        } else {
318
            if (keyword !== instruction.getInstruction()) {
11,550✔
319
                let range = instruction.getInstructionRange();
85✔
320
                // warn about uppercase convention if the keyword doesn't match the actual instruction
321
                let diagnostic = this.createUppercaseInstruction(range.start.line, range.start, range.end);
85✔
322
                if (diagnostic) {
85✔
323
                    problems.push(diagnostic);
80✔
324
                }
325
            }
326

327
            if (keyword === "MAINTAINER") {
11,550✔
328
                let range = instruction.getInstructionRange();
82✔
329
                let diagnostic = this.createMaintainerDeprecated(range.start.line, range.start, range.end);
82✔
330
                if (diagnostic) {
82✔
331
                    problems.push(diagnostic);
6✔
332
                }
333
            }
334

335
            const fullRange = instruction.getRange();
11,550✔
336
            if (fullRange.start.line !== fullRange.end.line && !isTrigger) {
11,550✔
337
                // if the instruction spans multiple lines, check for empty newlines
338
                const content = document.getText();
3,043✔
339
                const endingLine = fullRange.end.line;
3,043✔
340
                const skippedLines = this.getHeredocLines(instruction);
3,043✔
341
                let skipIndex = 0;
3,043✔
342
                let start = -1;
3,043✔
343
                for (let i = fullRange.start.line; i <= endingLine; i++) {
3,043✔
344
                    if (i === skippedLines[skipIndex]) {
6,850✔
345
                        skipIndex++;
25✔
346
                        continue;
25✔
347
                    }
348
                    const lineContent = content.substring(document.offsetAt(Position.create(i, 0)), document.offsetAt(Position.create(i + 1, 0)));
6,825✔
349
                    if (lineContent.trim().length === 0) {
6,825✔
350
                        if (start === -1) {
529✔
351
                            start = i;
523✔
352
                            continue;
523✔
353
                        }
354
                    } else if (start !== -1) {
6,296✔
355
                        const diagnostic = Validator.createEmptyContinuationLine(Position.create(start, 0), Position.create(i, 0), this.settings.emptyContinuationLine);
58✔
356
                        if (diagnostic) {
58✔
357
                            problems.push(diagnostic);
11✔
358
                        }
359
                        start = -1;
58✔
360
                    }
361
                }
362

363
                if (start !== -1) {
3,043✔
364
                    const diagnostic = Validator.createEmptyContinuationLine(Position.create(start, 0), Position.create(endingLine + 1, 0), this.settings.emptyContinuationLine);
465✔
365
                    if (diagnostic) {
465✔
366
                        problems.push(diagnostic);
2✔
367
                    }
368
                    start = -1;
465✔
369
                }
370
            }
371

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

519
                    const validFlags = ["interval", "retries", "start-period", "timeout", "start-interval"];
239✔
520
                    for (const flag of healthcheckFlags) {
239✔
521
                        const flagName = flag.getName();
136✔
522
                        if (validFlags.indexOf(flagName) === -1) {
136✔
523
                            const range = flag.getRange();
13✔
524
                            problems.push(Validator.createUnknownHealthcheckFlag(healthcheckInstructionRange.start.line, range.start, flagName === "" ? range.end : flag.getNameRange().end, flag.getName()));
13✔
525
                        } else if (flagName === "retries") {
123✔
526
                            const value = flag.getValue();
10✔
527
                            if (value) {
10✔
528
                                const valueRange = flag.getValueRange();
8✔
529
                                const integer = parseInt(value);
8✔
530
                                // test for NaN or numbers with decimals
531
                                if (isNaN(integer) || value.indexOf('.') !== -1) {
8✔
532
                                    problems.push(Validator.createInvalidSyntax(healthcheckInstructionRange.start.line, valueRange.start, valueRange.end, value));
2✔
533
                                } else if (integer < 1) {
6✔
534
                                    problems.push(Validator.createFlagAtLeastOne(healthcheckInstructionRange.start.line, valueRange.start, valueRange.end, "--retries", integer.toString()));
2✔
535
                                }
536
                            }
537
                        }
538
                    }
539

540
                    this.checkFlagValue(healthcheckInstructionRange.start.line, healthcheckFlags, validFlags, problems);
239✔
541
                    this.checkFlagDuration(healthcheckInstructionRange.start.line, healthcheckFlags, ["interval", "start-period", "timeout", "start-interval"], problems);
239✔
542
                    this.checkDuplicateFlags(healthcheckInstructionRange.start.line, healthcheckFlags, validFlags, problems);
239✔
543
                    break;
239✔
544
                case "ONBUILD":
545
                    this.checkArguments(instruction, problems, [-1], function (): any {
153✔
546
                        return null;
293✔
547
                    });
548
                    let onbuild = instruction as Onbuild;
153✔
549
                    let trigger = onbuild.getTrigger();
153✔
550
                    switch (trigger) {
153✔
551
                        case "FROM":
24✔
552
                        case "MAINTAINER":
553
                            problems.push(Validator.createOnbuildTriggerDisallowed(onbuild.getInstructionRange().start.line, onbuild.getTriggerRange(), trigger));
14✔
554
                            break;
14✔
555
                        case "ONBUILD":
556
                            problems.push(Validator.createOnbuildChainingDisallowed(onbuild.getInstructionRange().start.line, onbuild.getTriggerRange()));
5✔
557
                            break;
5✔
558
                    }
559
                    break;
153✔
560
                case "SHELL":
561
                    this.checkArguments(instruction, problems, [-1], function (): any {
110✔
562
                        return null;
175✔
563
                    });
564
                    this.checkJSON(document, instruction as JSONInstruction, problems);
110✔
565
                    break;
110✔
566
                case "STOPSIGNAL":
567
                    this.checkArguments(instruction, problems, [1], function (_index: number, argument: string) {
212✔
568
                        if (argument.indexOf("SIG") === 0 || argument.indexOf('$') != -1) {
154✔
569
                            return null;
97✔
570
                        }
571

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

693
                        let flag = copy.getFromFlag();
32✔
694
                        if (flag) {
32✔
695
                            let value = flag.getValue();
7✔
696
                            if (value !== null) {
7✔
697
                                let regexp = new RegExp(/^[a-zA-Z0-9].*$/);
6✔
698
                                if (!regexp.test(value)) {
6✔
699
                                    let range = value === "" ? flag.getRange() : flag.getValueRange();
3✔
700
                                    problems.push(Validator.createFlagInvalidFrom(copyInstructionRange.start.line, range.start, range.end, value));
3✔
701
                                }
702
                            }
703
                        }
704
                    }
705
                    const copyDestinationDiagnostic = this.checkDestinationIsDirectory(copy, Validator.createCOPYRequiresAtLeastTwoArguments, Validator.createCOPYDestinationNotDirectory);
163✔
706
                    if (copyDestinationDiagnostic !== null) {
163✔
707
                        problems.push(copyDestinationDiagnostic);
74✔
708
                    }
709
                    this.checkFlagValue(copyInstructionRange.start.line, flags, ["chmod", "chown", "from"], problems);
163✔
710
                    this.checkDuplicateFlags(copyInstructionRange.start.line, flags, ["chmod", "chown", "from", "link"], problems);
163✔
711
                    this.checkJSONQuotes(instruction, problems);
163✔
712
                    break;
163✔
713
                case "WORKDIR":
714
                    this.checkArguments(instruction, problems, [-1], function (): any {
235✔
715
                        return null;
201✔
716
                    });
717

718
                    let content = instruction.getArgumentsContent();
235✔
719
                    if (content) {
235✔
720
                        // strip out any surrounding quotes
721
                        const first = content.substring(0, 1);
185✔
722
                        const last = content.substring(content.length - 1);
185✔
723
                        if ((first === '\'' && last === '\'') || (first === '"' && last === '"')) {
185✔
724
                            content = content.substring(1, content.length - 1);
24✔
725
                        }
726
                        let regexp = new RegExp(/^(\$|([a-zA-Z](\$|:(\$|\\|\/)))).*$/);
185✔
727
                        if (!content.startsWith('/') && !regexp.test(content)) {
185✔
728
                            let problem = this.createWORKDIRNotAbsolute(instruction.getInstructionRange().start.line, instruction.getArgumentsRange());
32✔
729
                            if (problem) {
32✔
730
                                problems.push(problem);
24✔
731
                            }
732
                        }
733
                    }
734
                    break;
235✔
735
                default:
736
                    this.checkArguments(instruction, problems, [-1], function (): any {
209✔
737
                        return null;
109✔
738
                    });
739
                    break;
209✔
740
            }
741
        }
742
    }
743

744
    private hasHeredocs(args: Argument[]): boolean {
745
        for (const arg of args) {
62✔
746
            if (arg.getValue().startsWith("<<")) {
142✔
747
                return true;
28✔
748
            }
749
        }
750
        return false;
34✔
751
    }
752

753
    private getDestinationArgument(args: Argument[]): Argument {
754
        if (this.hasHeredocs(args)) {
33✔
755
            const initialLine = args[0].getRange().start.line;
16✔
756
            let candidate: Argument | null = null;
16✔
757
            for (let i = 1; i < args.length; i++) {
16✔
758
                if (args[i].getRange().start.line === initialLine) {
44✔
759
                    candidate = args[i];
28✔
760
                } else {
761
                    // stop searching once we're on another line
762
                    break;
16✔
763
                }
764
            }
765
            return candidate;
16✔
766
        }
767
        return args[args.length - 1];
17✔
768
    }
769

770
    private checkDestinationIsDirectory(instruction: JSONInstruction, requiresTwoArgumentsFunction: (instructionLine: uinteger, range: Range) => Diagnostic, notDirectoryFunction: (instructionLine: uinteger, range: Range) => Diagnostic): Diagnostic | null {
771
        if (instruction.getClosingBracket()) {
308✔
772
            return this.checkJsonDestinationIsDirectory(instruction, requiresTwoArgumentsFunction, notDirectoryFunction);
59✔
773
        }
774

775
        const args = instruction.getArguments();
249✔
776
        if (args.length === 1) {
249✔
777
            return requiresTwoArgumentsFunction(instruction.getInstructionRange().start.line, args[0].getRange());
4✔
778
        } else if (args.length === 0) {
245✔
779
            const instructionRange = instruction.getInstructionRange();
104✔
780
            return requiresTwoArgumentsFunction(instructionRange.start.line, instructionRange);
104✔
781
        } else if (args.length > 2) {
141✔
782
            const lastArg = this.getDestinationArgument(args);
33✔
783
            if (lastArg === null) {
33✔
784
                const instructionRange = instruction.getInstructionRange();
4✔
785
                return requiresTwoArgumentsFunction(instructionRange.start.line, instructionRange);
4✔
786
            } else if (this.hasHeredocs(args)) {
29✔
787
                return null;
12✔
788
            }
789
            const variables = instruction.getVariables();
17✔
790
            if (variables.length !== 0) {
17✔
791
                const lastJsonStringOffset = this.document.offsetAt(lastArg.getRange().end);
4✔
792
                const lastVarOffset = this.document.offsetAt(variables[variables.length - 1].getRange().end);
4✔
793
                if (lastJsonStringOffset === lastVarOffset || lastJsonStringOffset -1 === lastVarOffset) {
4!
794
                    return null;
4✔
795
                }
796
            }
797
            const destination = lastArg.getValue();
13✔
798
            const lastChar = destination.charAt(destination.length - 1);
13✔
799
            if (lastChar !== '\\' && lastChar !== '/') {
13✔
800
                return notDirectoryFunction(instruction.getInstructionRange().start.line, lastArg.getRange());
8✔
801
            }
802
        }
803
        return null;
113✔
804
    }
805

806
    private createDuplicatesDiagnostics(problems: Diagnostic[], severity: ValidationSeverity, instruction: string, instructions: Instruction[]): void {
807
        if (instructions.length > 1) {
34,467✔
808
            // decrement length by 1 because we want to ignore the last one
809
            for (let i = 0; i < instructions.length - 1; i++) {
40✔
810
                const instructionRange = instructions[i].getInstructionRange();
40✔
811
                const diagnostic = this.createMultipleInstructions(instructionRange.start.line, instructionRange, severity, instruction);
40✔
812
                if (diagnostic) {
40✔
813
                    problems.push(diagnostic);
36✔
814
                }
815
            }
816
        }
817
    }
818

819
    private createDuplicateBuildStageNameDiagnostics(problems: Diagnostic[], froms: From[]): void {
820
        const names: any = {};
5,699✔
821
        for (let from of froms) {
5,699✔
822
            let name = from.getBuildStage();
5,790✔
823
            if (name !== null) {
5,790✔
824
                name = name.toLowerCase();
36✔
825
                if (names[name] === undefined) {
36✔
826
                    names[name] = [from];
32✔
827
                } else {
828
                    names[name].push(from);
4✔
829
                }
830
            }
831
        }
832

833
        for (const name in names) {
5,699✔
834
            // duplicates found
835
            if (names[name].length > 1) {
32✔
836
                for (const from of names[name]) {
4✔
837
                    problems.push(Validator.createDuplicateBuildStageName(from.getInstructionRange().start.line, from.getBuildStageRange(), name));
8✔
838
                }
839
            }
840
        }
841
    }
842

843
    private checkJsonDestinationIsDirectory(instruction: JSONInstruction, requiresTwoArgumentsFunction: (instructionLine: uinteger, range: Range) => Diagnostic, notDirectoryFunction: (instructionLine: uinteger, range: Range) => Diagnostic): Diagnostic | null {
844
        const jsonStrings = instruction.getJSONStrings();
59✔
845
        if (jsonStrings.length === 0) {
59✔
846
            return requiresTwoArgumentsFunction(instruction.getInstructionRange().start.line, instruction.getArgumentsRange());
4✔
847
        } else if (jsonStrings.length === 1) {
55✔
848
            return requiresTwoArgumentsFunction(instruction.getInstructionRange().start.line, jsonStrings[0].getJSONRange());
6✔
849
        } else if (jsonStrings.length > 2) {
49✔
850
            const lastJsonString = jsonStrings[jsonStrings.length - 1];
37✔
851
            const variables = instruction.getVariables();
37✔
852
            if (variables.length !== 0) {
37✔
853
                const lastVar = variables[variables.length - 1];
12✔
854
                const lastJsonStringOffset = this.document.offsetAt(lastJsonString.getRange().end);
12✔
855
                const lastVarOffset = this.document.offsetAt(lastVar.getRange().end);
12✔
856
                if (lastJsonStringOffset === lastVarOffset || lastJsonStringOffset -1 === lastVarOffset) {
12!
857
                    return null;
12✔
858
                }
859
            }
860
            const destination = lastJsonString.getValue();
25✔
861
            const lastChar = destination.charAt(destination.length - 2);
25✔
862
            if (lastChar !== '\\' && lastChar !== '/') {
25✔
863
                return notDirectoryFunction(instruction.getInstructionRange().start.line, jsonStrings[jsonStrings.length - 1].getJSONRange());
13✔
864
            }
865
        }
866
        return null;
24✔
867
    }
868

869
    private checkFlagValue(instructionLine: uinteger, flags: Flag[], validFlagNames: string[], problems: Diagnostic[]): void {
870
        for (let flag of flags) {
6,342✔
871
            let flagName = flag.getName();
210✔
872
            // only validate flags with the right name
873
            if (flag.getValue() === null && validFlagNames.indexOf(flagName) !== -1) {
210✔
874
                problems.push(Validator.createFlagMissingValue(instructionLine, flag.getNameRange(), flagName));
11✔
875
            }
876
        }
877
    }
878

879
    /**
880
     * Checks that the given boolean flag is valid. A boolean flag should
881
     * either have no value defined (--flag) or the value should
882
     * case-insensitively be either "true" or "false" (--flag==tRUe is
883
     * valid).
884
     */
885
    private checkFlagBoolean(instructionLine: uinteger, flag: Flag): Diagnostic | null {
886
        const linkValue = flag.getValue();
18✔
887
        if (linkValue === "") {
18✔
888
            return Validator.createFlagMissingValue(instructionLine, flag.getNameRange(), flag.getName());
2✔
889
        } else if (linkValue !== null) {
16✔
890
            const convertedLinkValue = linkValue.toLowerCase();
14✔
891
            if (convertedLinkValue !== "true" && convertedLinkValue !== "false") {
14✔
892
                return Validator.createFlagInvalidLink(instructionLine, flag.getValueRange(), linkValue);
2✔
893
            }
894
        }
895
        return null;
14✔
896
    }
897

898
    private checkFlagDuration(instructionLine: uinteger, flags: Flag[], validFlagNames: string[], problems: Diagnostic[]): void {
899
        flagCheck: for (let flag of flags) {
239✔
900
            let flagName = flag.getName();
136✔
901
            // only validate flags with the right name
902
            if (validFlagNames.indexOf(flagName) !== -1) {
136✔
903
                let value = flag.getValue();
113✔
904
                if (value !== null && value.length !== 0) {
113✔
905
                    switch (value.charAt(0)) {
105✔
906
                        case '0':
735✔
907
                        case '1':
908
                        case '2':
909
                        case '3':
910
                        case '4':
911
                        case '5':
912
                        case '6':
913
                        case '7':
914
                        case '8':
915
                        case '9':
916
                        case '.':
917
                        case '-':
918
                            break;
99✔
919
                        default:
920
                            let range = flag.getValueRange();
6✔
921
                            problems.push(Validator.createFlagInvalidDuration(instructionLine, range.start, range.end, value));
6✔
922
                            continue flagCheck;
6✔
923
                    }
924

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

1075
                    if (!durationSpecified) {
60✔
1076
                        let range = flag.getValueRange();
9✔
1077
                        problems.push(Validator.createFlagMissingDuration(instructionLine, range.start, range.end, value));
9✔
1078
                    } else if (duration < 1) {
51✔
1079
                        let range = flag.getNameRange();
15✔
1080
                        problems.push(Validator.createFlagLessThan1ms(instructionLine, range.start, range.end, flagName));
15✔
1081
                    }
1082
                }
1083
            }
1084
        }
1085
    }
1086

1087
    private static isNumberRelated(character: string) {
1088
        switch (character) {
70✔
1089
            case '.':
270✔
1090
            case '0':
1091
            case '1':
1092
            case '2':
1093
            case '3':
1094
            case '4':
1095
            case '5':
1096
            case '6':
1097
            case '7':
1098
            case '8':
1099
            case '9':
1100
                return true;
36✔
1101
        }
1102
        return false;
34✔
1103
    }
1104

1105
    private checkDuplicateFlags(instructionLine: uinteger, flags: Flag[], validFlags: string[], problems: Diagnostic[]): void {
1106
        let flagNames = flags.map(function (flag) {
547✔
1107
            return flag.getName();
204✔
1108
        });
1109
        for (let validFlag of validFlags) {
547✔
1110
            let index = flagNames.indexOf(validFlag);
2,282✔
1111
            let lastIndex = flagNames.lastIndexOf(validFlag);
2,282✔
1112
            if (index !== lastIndex) {
2,282✔
1113
                let range = flags[index].getNameRange();
8✔
1114
                problems.push(Validator.createFlagDuplicate(instructionLine, range.start, range.end, flagNames[index]));
8✔
1115
                range = flags[lastIndex].getNameRange();
8✔
1116
                problems.push(Validator.createFlagDuplicate(instructionLine, range.start, range.end, flagNames[index]));
8✔
1117
            }
1118
        }
1119
    }
1120

1121
    private checkJSON(document: TextDocument, instruction: JSONInstruction, problems: Diagnostic[]) {
1122
        let argsContent = instruction.getArgumentsContent();
110✔
1123
        if (argsContent === null) {
110✔
1124
            return;
50✔
1125
        }
1126

1127
        let argsRange = instruction.getArgumentsRange();
60✔
1128
        let args = instruction.getArguments();
60✔
1129
        if ((args.length === 1 && args[0].getValue() === "[]") ||
60✔
1130
            (args.length === 2 && args[0].getValue() === '[' && args[1].getValue() === ']')) {
1131
            problems.push(Validator.createShellRequiresOne(instruction.getInstructionRange().start.line, argsRange));
3✔
1132
            return;
3✔
1133
        }
1134

1135
        const closing = instruction.getClosingBracket();
57✔
1136
        if (closing) {
57✔
1137
            let content = document.getText();
45✔
1138
            content = content.substring(
45✔
1139
                document.offsetAt(instruction.getOpeningBracket().getRange().end),
1140
                document.offsetAt(closing.getRange().start)
1141
            );
1142
            content = content.trim();
45✔
1143
            if (content.charAt(content.length - 1) !== '"') {
45✔
1144
                problems.push(Validator.createShellJsonForm(instruction.getInstructionRange().start.line, argsRange));
1✔
1145
            }
1146
        } else {
1147
            problems.push(Validator.createShellJsonForm(instruction.getInstructionRange().start.line, argsRange));
12✔
1148
        }
1149
    }
1150

1151
    private checkJSONQuotes(instruction: Instruction, problems: Diagnostic[]) {
1152
        let argsContent = instruction.getArgumentsContent();
761✔
1153
        if (argsContent === null) {
761✔
1154
            return;
308✔
1155
        }
1156

1157
        let argsRange = instruction.getArgumentsRange();
453✔
1158
        let args = instruction.getArguments();
453✔
1159
        if ((args.length === 1 && args[0].getValue() === "[]") ||
453✔
1160
            (args.length === 2 && args[0].getValue() === '[' && args[1].getValue() === ']')) {
1161
            return;
4✔
1162
        }
1163

1164
        let jsonLike = false;
449✔
1165
        let last: string = null;
449✔
1166
        let quoted = false;
449✔
1167
        argsCheck: for (let i = 0; i < argsContent.length; i++) {
449✔
1168
            switch (argsContent.charAt(i)) {
1,354✔
1169
                case '[':
1,481✔
1170
                    if (last === null) {
147✔
1171
                        last = '[';
145✔
1172
                        jsonLike = true;
145✔
1173
                    }
1174
                    break;
147✔
1175
                case '\'':
1176
                    if (last === '[' || last === ',') {
151✔
1177
                        quoted = true;
77✔
1178
                        last = '\'';
77✔
1179
                        continue;
77✔
1180
                    } else if (last === '\'') {
74✔
1181
                        if (quoted) {
73✔
1182
                            // quoted string done
1183
                            quoted = false;
72✔
1184
                        } else {
1185
                            // should be a , or a ]
1186
                            break argsCheck;
1✔
1187
                        }
1188
                    } else {
1189
                        break argsCheck;
1✔
1190
                    }
1191
                    break;
72✔
1192
                case ',':
1193
                    if (!jsonLike) {
54✔
1194
                        break argsCheck;
1✔
1195
                    } else if (!quoted) {
53✔
1196
                        if (last === '\'') {
46✔
1197
                            last = ','
45✔
1198
                        } else {
1199
                            break argsCheck;
1✔
1200
                        }
1201
                    }
1202
                    break;
52✔
1203
                case ']':
1204
                    if (!quoted) {
49✔
1205
                        if (last === '\'' || last === ',') {
37✔
1206
                            last = null;
36✔
1207
                            const problem = Validator.createJSONInSingleQuotes(instruction.getInstructionRange().start.line, argsRange, this.settings.instructionJSONInSingleQuotes);
36✔
1208
                            if (problem) {
36✔
1209
                                problems.push(problem);
30✔
1210
                            }
1211
                        }
1212
                        break argsCheck;
37✔
1213
                    }
1214
                    break;
12✔
1215
                case ' ':
1216
                case '\t':
1217
                    break;
127✔
1218
                default:
1219
                    if (!quoted) {
826✔
1220
                        break argsCheck;
400✔
1221
                    }
1222
                    break;
426✔
1223
            }
1224
        }
1225
    }
1226

1227
    private static dockerProblems = {
1✔
1228
        "baseNameEmpty": "base name (${0}) should not be blank",
1229

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

1234
        "noSourceImage": "No source image provided with `FROM`",
1235

1236
        "emptyContinuationLine": "Empty continuation line",
1237

1238
        "fromRequiresOneOrThreeArguments": "FROM requires either one or three arguments",
1239

1240
        "invalidAs": "Second argument should be AS",
1241
        "invalidPort": "Invalid containerPort: ${0}",
1242
        "invalidProtocol": "Invalid proto: ${0}",
1243
        "invalidReferenceFormat": "invalid reference format",
1244
        "invalidStopSignal": "Invalid signal: ${0}",
1245
        "invalidSyntax": "parsing \"${0}\": invalid syntax",
1246
        "invalidDestination": "When using ${0} with more than one source file, the destination must be a directory and end with a / or a \\",
1247

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

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

1256
        "flagAtLeastOne": "${0} must be at least 1 (not ${1})",
1257
        "flagDuplicate": "Duplicate flag specified: ${0}",
1258
        "flagInvalidDuration": "time: invalid duration ${0}",
1259
        "flagInvalidFrom": "invalid from flag value ${0}: invalid reference format",
1260
        "flagInvalidLink": "expecting boolean value for flag link, not: ${0}",
1261
        "flagLessThan1ms": "Interval \"${0}\" cannot be less than 1ms",
1262
        "flagMissingDuration": "time: missing unit in duration ${0}",
1263
        "flagMissingValue": "Missing a value on flag: ${0}",
1264
        "flagUnknown": "Unknown flag: ${0}",
1265
        "flagUnknownUnit": "time: unknown unit ${0} in duration ${1}",
1266

1267
        "instructionExtraArgument": "Instruction has an extra argument",
1268
        "instructionMissingArgument": "Instruction has no arguments",
1269
        "instructionMultiple": "There can only be one ${0} instruction in a Dockerfile or build stage. Only the last one will have an effect.",
1270
        "instructionRequiresOneArgument": "${0} requires exactly one argument",
1271
        "instructionRequiresAtLeastOneArgument": "${0} requires at least one argument",
1272
        "instructionRequiresAtLeastTwoArguments": "${0} requires at least two arguments",
1273
        "instructionRequiresTwoArguments": "${0} must have two arguments",
1274
        "instructionUnnecessaryArgument": "${0} takes no arguments",
1275
        "instructionUnknown": "Unknown instruction: ${0}",
1276
        "instructionCasing": "Instructions should be written in uppercase letters",
1277
        "instructionJSONInSingleQuotes": "Instruction written as a JSON array but is using single quotes instead of double quotes",
1278

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

1281
        "onbuildChainingDisallowed": "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed",
1282
        "onbuildTriggerDisallowed": "${0} isn't allowed as an ONBUILD trigger",
1283

1284
        "shellJsonForm": "SHELL requires the arguments to be in JSON form",
1285
        "shellRequiresOne": "SHELL requires at least one argument",
1286

1287
        "deprecatedMaintainer": "MAINTAINER has been deprecated",
1288

1289
        "healthcheckCmdArgumentMissing": "Missing command after HEALTHCHECK CMD",
1290
        "healthcheckTypeUnknown": "Unknown type\"${0}\" in HEALTHCHECK (try CMD)",
1291

1292
        "workdirPathNotAbsolute": "WORKDIR paths should be absolute"
1293
    };
1294

1295
    private static formatMessage(text: string, ...variables: string[]): string {
1296
        for (let i = 0; i < variables.length; i++) {
1,178✔
1297
            text = text.replace("${" + i + "}", variables[i]);
1,254✔
1298
        }
1299
        return text;
1,178✔
1300
    }
1301

1302
    public static getDiagnosticMessage_DirectiveCasing() {
1303
        return Validator.dockerProblems["directiveCasing"];
10✔
1304
    }
1305

1306
    public static getDiagnosticMessage_DirectiveEscapeDuplicated() {
1307
        return Validator.dockerProblems["directiveEscapeDuplicated"];
2✔
1308
    }
1309

1310
    public static getDiagnosticMessage_DirectiveEscapeInvalid(value: string) {
1311
        return Validator.formatMessage(Validator.dockerProblems["directiveEscapeInvalid"], value);
8✔
1312
    }
1313

1314
    public static getDiagnosticMessage_NoSourceImage() {
1315
        return Validator.dockerProblems["noSourceImage"];
34✔
1316
    }
1317

1318
    public static getDiagnosticMessage_EmptyContinuationLine() {
1319
        return Validator.dockerProblems["emptyContinuationLine"];
26✔
1320
    }
1321

1322
    public static getDiagnosticMessage_DuplicateBuildStageName(name: string) {
1323
        return Validator.formatMessage(Validator.dockerProblems["duplicateBuildStageName"], name);
16✔
1324
    }
1325

1326
    public static getDiagnosticMessage_InvalidBuildStageName(name: string) {
1327
        return Validator.formatMessage(Validator.dockerProblems["invalidBuildStageName"], name);
6✔
1328
    }
1329

1330
    public static getDiagnosticMessage_FlagAtLeastOne(flagName: string, flagValue: string) {
1331
        return Validator.formatMessage(Validator.dockerProblems["flagAtLeastOne"], flagName, flagValue);
4✔
1332
    }
1333

1334
    public static getDiagnosticMessage_FlagDuplicate(flag: string) {
1335
        return Validator.formatMessage(Validator.dockerProblems["flagDuplicate"], flag);
32✔
1336
    }
1337

1338
    public static getDiagnosticMessage_FlagInvalidDuration(flag: string) {
1339
        return Validator.formatMessage(Validator.dockerProblems["flagInvalidDuration"], flag);
18✔
1340
    }
1341

1342
    public static getDiagnosticMessage_FlagLessThan1ms(flag: string) {
1343
        return Validator.formatMessage(Validator.dockerProblems["flagLessThan1ms"], flag);
54✔
1344
    }
1345

1346
    public static getDiagnosticMessage_FlagMissingDuration(duration: string) {
1347
        return Validator.formatMessage(Validator.dockerProblems["flagMissingDuration"], duration);
36✔
1348
    }
1349

1350
    public static getDiagnosticMessage_FlagInvalidFromValue(value: string): string {
1351
        return Validator.formatMessage(Validator.dockerProblems["flagInvalidFrom"], value);
6✔
1352
    }
1353

1354
    public static getDiagnosticMessage_FlagInvalidLinkValue(value: string): string {
1355
        return Validator.formatMessage(Validator.dockerProblems["flagInvalidLink"], value);
4✔
1356
    }
1357

1358
    public static getDiagnosticMessage_FlagMissingValue(flag: string) {
1359
        return Validator.formatMessage(Validator.dockerProblems["flagMissingValue"], flag);
26✔
1360
    }
1361

1362
    public static getDiagnosticMessage_FlagUnknown(flag: string) {
1363
        return Validator.formatMessage(Validator.dockerProblems["flagUnknown"], flag);
62✔
1364
    }
1365

1366
    public static getDiagnosticMessage_FlagUnknownUnit(unit: string, duration: string) {
1367
        return Validator.formatMessage(Validator.dockerProblems["flagUnknownUnit"], unit, duration);
30✔
1368
    }
1369

1370
    public static getDiagnosticMessage_BaseNameEmpty(name: string) {
1371
        return Validator.formatMessage(Validator.dockerProblems["baseNameEmpty"], name);
4✔
1372
    }
1373

1374
    public static getDiagnosticMessage_InvalidAs() {
1375
        return Validator.dockerProblems["invalidAs"];
2✔
1376
    }
1377

1378
    public static getDiagnosticMessage_InvalidPort(port: string) {
1379
        return Validator.formatMessage(Validator.dockerProblems["invalidPort"], port);
54✔
1380
    }
1381

1382
    public static getDiagnosticMessage_InvalidProto(protocol: string) {
1383
        return Validator.formatMessage(Validator.dockerProblems["invalidProtocol"], protocol);
8✔
1384
    }
1385

1386
    public static getDiagnosticMessage_InvalidReferenceFormat() {
1387
        return Validator.dockerProblems["invalidReferenceFormat"];
82✔
1388
    }
1389

1390
    public static getDiagnosticMessage_InvalidSignal(signal: string) {
1391
        return Validator.formatMessage(Validator.dockerProblems["invalidStopSignal"], signal);
14✔
1392
    }
1393

1394
    public static getDiagnosticMessage_InvalidSyntax(syntax: string) {
1395
        return Validator.formatMessage(Validator.dockerProblems["invalidSyntax"], syntax);
4✔
1396
    }
1397

1398
    public static getDiagnosticMessage_InstructionExtraArgument() {
1399
        return Validator.dockerProblems["instructionExtraArgument"];
16✔
1400
    }
1401

1402
    public static getDiagnosticMessage_InstructionMissingArgument() {
1403
        return Validator.dockerProblems["instructionMissingArgument"];
1,304✔
1404
    }
1405

1406
    public static getDiagnosticMessage_ADDDestinationNotDirectory() {
1407
        return Validator.formatMessage(Validator.dockerProblems["invalidDestination"], "ADD");
16✔
1408
    }
1409

1410
    public static getDiagnosticMessage_ADDRequiresAtLeastTwoArguments() {
1411
        return Validator.formatMessage(Validator.dockerProblems["instructionRequiresAtLeastTwoArguments"], "ADD");
122✔
1412
    }
1413

1414
    public static getDiagnosticMessage_COPYDestinationNotDirectory() {
1415
        return Validator.formatMessage(Validator.dockerProblems["invalidDestination"], "COPY");
26✔
1416
    }
1417

1418
    public static getDiagnosticMessage_COPYRequiresAtLeastTwoArguments() {
1419
        return Validator.formatMessage(Validator.dockerProblems["instructionRequiresAtLeastTwoArguments"], "COPY");
122✔
1420
    }
1421

1422
    public static getDiagnosticMessage_HEALTHCHECKRequiresAtLeastOneArgument() {
1423
        return Validator.formatMessage(Validator.dockerProblems["instructionRequiresAtLeastOneArgument"], "HEALTHCHECK");
106✔
1424
    }
1425

1426
    public static getDiagnosticMessage_ENVRequiresTwoArguments() {
1427
        return Validator.formatMessage(Validator.dockerProblems["instructionRequiresTwoArguments"], "ENV");
4✔
1428
    }
1429

1430
    public static getDiagnosticMessage_InstructionRequiresOneOrThreeArguments() {
1431
        return Validator.dockerProblems["fromRequiresOneOrThreeArguments"];
28✔
1432
    }
1433

1434
    public static getDiagnosticMessage_HealthcheckNoneUnnecessaryArgument() {
1435
        return Validator.formatMessage(Validator.dockerProblems["instructionUnnecessaryArgument"], "HEALTHCHECK NONE");
4✔
1436
    }
1437

1438
    public static getDiagnosticMessage_InstructionMultiple(instruction: string) {
1439
        return Validator.formatMessage(Validator.dockerProblems["instructionMultiple"], instruction);
72✔
1440
    }
1441

1442
    public static getDiagnosticMessage_InstructionUnknown(instruction: string) {
1443
        return Validator.formatMessage(Validator.dockerProblems["instructionUnknown"], instruction);
38✔
1444
    }
1445

1446
    public static getDiagnosticMessage_SyntaxMissingEquals(argument: string) {
1447
        return Validator.formatMessage(Validator.dockerProblems["syntaxMissingEquals"], argument);
8✔
1448
    }
1449

1450
    public static getDiagnosticMessage_SyntaxMissingNames(instruction: string) {
1451
        return Validator.formatMessage(Validator.dockerProblems["syntaxMissingNames"], instruction);
20✔
1452
    }
1453

1454
    public static getDiagnosticMessage_SyntaxMissingSingleQuote(key: string) {
1455
        return Validator.formatMessage(Validator.dockerProblems["syntaxMissingSingleQuote"], key);
24✔
1456
    }
1457

1458
    public static getDiagnosticMessage_SyntaxMissingDoubleQuote(key: string) {
1459
        return Validator.formatMessage(Validator.dockerProblems["syntaxMissingDoubleQuote"], key);
54✔
1460
    }
1461

1462
    public static getDiagnosticMessage_InstructionCasing() {
1463
        return Validator.dockerProblems["instructionCasing"];
160✔
1464
    }
1465

1466
    public static getDiagnosticMessage_InstructionJSONInSingleQuotes() {
1467
        return Validator.dockerProblems["instructionJSONInSingleQuotes"];
60✔
1468
    }
1469

1470
    public static getDiagnosticMessage_OnbuildChainingDisallowed() {
1471
        return Validator.dockerProblems["onbuildChainingDisallowed"];
10✔
1472
    }
1473

1474
    public static getDiagnosticMessage_OnbuildTriggerDisallowed(trigger: string) {
1475
        return Validator.formatMessage(Validator.dockerProblems["onbuildTriggerDisallowed"], trigger);
28✔
1476
    }
1477

1478
    public static getDiagnosticMessage_VariableModifierUnsupported(variable: string, modifier: string) {
1479
        return Validator.formatMessage(Validator.dockerProblems["variableModifierUnsupported"], variable, modifier);
90✔
1480
    }
1481

1482
    public static getDiagnosticMessage_ShellJsonForm() {
1483
        return Validator.dockerProblems["shellJsonForm"];
26✔
1484
    }
1485

1486
    public static getDiagnosticMessage_ShellRequiresOne() {
1487
        return Validator.dockerProblems["shellRequiresOne"];
6✔
1488
    }
1489

1490
    public static getDiagnosticMessage_DeprecatedMaintainer() {
1491
        return Validator.dockerProblems["deprecatedMaintainer"];
12✔
1492
    }
1493

1494
    public static getDiagnosticMessage_HealthcheckCmdArgumentMissing() {
1495
        return Validator.dockerProblems["healthcheckCmdArgumentMissing"];
6✔
1496
    }
1497

1498
    public static getDiagnosticMessage_HealthcheckTypeUnknown(type: string) {
1499
        return Validator.formatMessage(Validator.dockerProblems["healthcheckTypeUnknown"], type);
10✔
1500
    }
1501

1502
    public static getDiagnosticMessage_WORKDIRPathNotAbsolute() {
1503
        return Validator.formatMessage(Validator.dockerProblems["workdirPathNotAbsolute"]);
48✔
1504
    }
1505

1506
    private static createDuplicatedEscapeDirective(start: Position, end: Position): Diagnostic {
1507
        return Validator.createError(null, start, end, Validator.getDiagnosticMessage_DirectiveEscapeDuplicated(), ValidationCode.DUPLICATED_ESCAPE_DIRECTIVE, [DiagnosticTag.Unnecessary]);
1✔
1508
    }
1509

1510
    private static createInvalidEscapeDirective(start: Position, end: Position, value: string): Diagnostic {
1511
        return Validator.createError(null, start, end, Validator.getDiagnosticMessage_DirectiveEscapeInvalid(value), ValidationCode.INVALID_ESCAPE_DIRECTIVE);
4✔
1512
    }
1513

1514
    private static createDuplicateBuildStageName(instructionLine: uinteger, range: Range, name: string): Diagnostic {
1515
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_DuplicateBuildStageName(name), ValidationCode.DUPLICATE_BUILD_STAGE_NAME);
8✔
1516
    }
1517

1518
    private static createInvalidBuildStageName(instructionLine: uinteger | null, range: Range, name: string): Diagnostic {
1519
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InvalidBuildStageName(name), ValidationCode.INVALID_BUILD_STAGE_NAME);
3✔
1520
    }
1521

1522
    private static createFlagAtLeastOne(instructionLine: uinteger | null, start: Position, end: Position, flagName: string, flagValue: string): Diagnostic {
1523
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagAtLeastOne(flagName, flagValue), ValidationCode.FLAG_AT_LEAST_ONE);
2✔
1524
    }
1525

1526
    private static createFlagDuplicate(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1527
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagDuplicate(flag), ValidationCode.FLAG_DUPLICATE);
16✔
1528
    }
1529

1530
    private static createFlagInvalidDuration(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1531
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagInvalidDuration(flag), ValidationCode.FLAG_INVALID_DURATION);
9✔
1532
    }
1533

1534
    private static createFlagLessThan1ms(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1535
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagLessThan1ms(flag), ValidationCode.FLAG_LESS_THAN_1MS);
27✔
1536
    }
1537

1538
    private static createFlagMissingDuration(instructionLine: uinteger | null, start: Position, end: Position, duration: string): Diagnostic {
1539
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagMissingDuration(duration), ValidationCode.FLAG_MISSING_DURATION);
18✔
1540
    }
1541

1542
    private static createFlagInvalidFrom(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1543
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagInvalidFromValue(flag), ValidationCode.FLAG_INVALID_FROM_VALUE);
3✔
1544
    }
1545

1546
    private static createFlagInvalidLink(instructionLine: uinteger | null, range: Range, value: string): Diagnostic {
1547
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_FlagInvalidLinkValue(value), ValidationCode.FLAG_INVALID_LINK_VALUE);
2✔
1548
    }
1549

1550
    private static createFlagMissingValue(instructionLine: uinteger | null, range: Range, flag: string): Diagnostic {
1551
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_FlagMissingValue(flag), ValidationCode.FLAG_MISSING_VALUE);
13✔
1552
    }
1553

1554
    private static createUnknownAddFlag(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1555
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagUnknown(flag), ValidationCode.UNKNOWN_ADD_FLAG);
7✔
1556
    }
1557

1558
    private static createUnknownCopyFlag(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1559
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagUnknown(flag), ValidationCode.UNKNOWN_COPY_FLAG);
8✔
1560
    }
1561

1562
    private static createUnknownFromFlag(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1563
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagUnknown(flag), ValidationCode.UNKNOWN_FROM_FLAG);
3✔
1564
    }
1565

1566
    private static createUnknownHealthcheckFlag(instructionLine: uinteger | null, start: Position, end: Position, flag: string): Diagnostic {
1567
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_FlagUnknown(flag), ValidationCode.UNKNOWN_HEALTHCHECK_FLAG);
13✔
1568
    }
1569

1570
    private static createFlagUnknownUnit(instructionLine: uinteger | null, range: Range, unit: string, duration: string): Diagnostic {
1571
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_FlagUnknownUnit(unit, duration), ValidationCode.FLAG_UNKNOWN_UNIT);
15✔
1572
    }
1573

1574
    private static createBaseNameEmpty(instructionLine: uinteger | null, range: Range, name: string): Diagnostic {
1575
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_BaseNameEmpty(name), ValidationCode.BASE_NAME_EMPTY);
2✔
1576
    }
1577

1578
    private static createInvalidAs(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1579
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InvalidAs(), ValidationCode.INVALID_AS);
1✔
1580
    }
1581

1582
    private static createInvalidPort(instructionLine: uinteger | null, range: Range, port: string): Diagnostic {
1583
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InvalidPort(port), ValidationCode.INVALID_PORT);
27✔
1584
    }
1585

1586
    private static createInvalidProto(instructionLine: uinteger | null, start: Position, end: Position, protocol: string): Diagnostic {
1587
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InvalidProto(protocol), ValidationCode.INVALID_PROTO);
4✔
1588
    }
1589

1590
    private static createInvalidReferenceFormat(instructionLine: uinteger | null, range: Range): Diagnostic {
1591
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InvalidReferenceFormat(), ValidationCode.INVALID_REFERENCE_FORMAT);
41✔
1592
    }
1593

1594
    private static createInvalidStopSignal(instructionLine: uinteger | null, start: Position, end: Position, signal: string): Diagnostic {
1595
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InvalidSignal(signal), ValidationCode.INVALID_SIGNAL);
7✔
1596
    }
1597

1598
    private static createInvalidSyntax(instructionLine: uinteger | null, start: Position, end: Position, syntax: string): Diagnostic {
1599
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InvalidSyntax(syntax), ValidationCode.INVALID_SYNTAX);
2✔
1600
    }
1601

1602
    private static createMissingArgument(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1603
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionMissingArgument(), ValidationCode.ARGUMENT_MISSING);
652✔
1604
    }
1605

1606
    private static createExtraArgument(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1607
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionExtraArgument(), ValidationCode.ARGUMENT_EXTRA);
8✔
1608
    }
1609

1610
    private static createHealthcheckNoneUnnecessaryArgument(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1611
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_HealthcheckNoneUnnecessaryArgument(), ValidationCode.ARGUMENT_UNNECESSARY);
2✔
1612
    }
1613

1614
    private static createADDDestinationNotDirectory(instructionLine: uinteger | null, range: Range): Diagnostic {
1615
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_ADDDestinationNotDirectory(), ValidationCode.INVALID_DESTINATION);
8✔
1616
    }
1617

1618
    private static createADDRequiresAtLeastTwoArguments(instructionLine: uinteger | null, range: Range): Diagnostic {
1619
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_ADDRequiresAtLeastTwoArguments(), ValidationCode.ARGUMENT_REQUIRES_AT_LEAST_TWO);
61✔
1620
    }
1621

1622
    private static createCOPYDestinationNotDirectory(instructionLine: uinteger | null, range: Range): Diagnostic {
1623
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_COPYDestinationNotDirectory(), ValidationCode.INVALID_DESTINATION);
13✔
1624
    }
1625

1626
    private static createCOPYRequiresAtLeastTwoArguments(instructionLine: uinteger | null, range: Range): Diagnostic {
1627
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_COPYRequiresAtLeastTwoArguments(), ValidationCode.ARGUMENT_REQUIRES_AT_LEAST_TWO);
61✔
1628
    }
1629

1630
    private static createENVRequiresTwoArguments(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1631
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_ENVRequiresTwoArguments(), ValidationCode.ARGUMENT_REQUIRES_TWO);
2✔
1632
    }
1633

1634
    private static createHEALTHCHECKRequiresAtLeastOneArgument(instructionLine: uinteger | null, range: Range): Diagnostic {
1635
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_HEALTHCHECKRequiresAtLeastOneArgument(), ValidationCode.ARGUMENT_REQUIRES_AT_LEAST_ONE);
53✔
1636
    }
1637

1638
    private static createHealthcheckCmdArgumentMissing(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1639
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_HealthcheckCmdArgumentMissing(), ValidationCode.HEALTHCHECK_CMD_ARGUMENT_MISSING);
3✔
1640
    }
1641

1642
    private static createHealthcheckTypeUnknown(instructionLine: uinteger | null, range: Range, type: string): Diagnostic {
1643
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_HealthcheckTypeUnknown(type), ValidationCode.UNKNOWN_TYPE);
5✔
1644
    }
1645

1646
    private static createOnbuildChainingDisallowed(instructionLine: uinteger | null, range: Range): Diagnostic {
1647
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_OnbuildChainingDisallowed(), ValidationCode.ONBUILD_CHAINING_DISALLOWED);
5✔
1648
    }
1649

1650
    private static createOnbuildTriggerDisallowed(instructionLine: uinteger | null, range: Range, trigger: string): Diagnostic {
1651
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_OnbuildTriggerDisallowed(trigger), ValidationCode.ONBUILD_TRIGGER_DISALLOWED);
14✔
1652
    }
1653

1654
    private static createShellJsonForm(instructionLine: uinteger | null, range: Range): Diagnostic {
1655
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_ShellJsonForm(), ValidationCode.SHELL_JSON_FORM);
13✔
1656
    }
1657

1658
    private static createShellRequiresOne(instructionLine: uinteger | null, range: Range): Diagnostic {
1659
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_ShellRequiresOne(), ValidationCode.SHELL_REQUIRES_ONE);
3✔
1660
    }
1661

1662
    private static createRequiresOneOrThreeArguments(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic {
1663
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionRequiresOneOrThreeArguments(), ValidationCode.ARGUMENT_REQUIRES_ONE_OR_THREE);
14✔
1664
    }
1665

1666
    private static createNoSourceImage(start: Position, end: Position): Diagnostic {
1667
        return Validator.createError(null, start, end, Validator.getDiagnosticMessage_NoSourceImage(), ValidationCode.NO_SOURCE_IMAGE);
17✔
1668
    }
1669

1670
    private static createSyntaxMissingEquals(instructionLine: uinteger | null, start: Position, end: Position, argument: string): Diagnostic {
1671
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_SyntaxMissingEquals(argument), ValidationCode.SYNTAX_MISSING_EQUALS);
4✔
1672
    }
1673

1674
    private static createSyntaxMissingSingleQuote(instructionLine: uinteger | null, start: Position, end: Position, argument: string): Diagnostic {
1675
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_SyntaxMissingSingleQuote(argument), ValidationCode.SYNTAX_MISSING_SINGLE_QUOTE);
12✔
1676
    }
1677

1678
    private static createSyntaxMissingDoubleQuote(instructionLine: uinteger | null, start: Position, end: Position, argument: string): Diagnostic {
1679
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_SyntaxMissingDoubleQuote(argument), ValidationCode.SYNTAX_MISSING_DOUBLE_QUOTE);
27✔
1680
    }
1681

1682
    private static createSyntaxMissingNames(instructionLine: uinteger | null, start: Position, end: Position, instruction: string): Diagnostic {
1683
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_SyntaxMissingNames(instruction), ValidationCode.SYNTAX_MISSING_NAMES);
10✔
1684
    }
1685

1686
    private static createVariableUnsupportedModifier(instructionLine: uinteger | null, range: Range, variable: string, modifier: string): Diagnostic {
1687
        return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_VariableModifierUnsupported(variable, modifier), ValidationCode.UNSUPPORTED_MODIFIER);
45✔
1688
    }
1689

1690
    private static createUnknownInstruction(instructionLine: uinteger | null, start: Position, end: Position, instruction: string): Diagnostic {
1691
        return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionUnknown(instruction), ValidationCode.UNKNOWN_INSTRUCTION);
19✔
1692
    }
1693

1694
    private static createError(instructionLine: uinteger | null, start: Position, end: Position, description: string, code?: ValidationCode, tags?: DiagnosticTag[]): Diagnostic {
1695
        return Validator.createDiagnostic(DiagnosticSeverity.Error, instructionLine, start, end, description, code, tags);
1,325✔
1696
    }
1697

1698
    private static createJSONInSingleQuotes(instructionLine: uinteger | null, range: Range, severity: ValidationSeverity | undefined): Diagnostic | null {
1699
        if (severity === ValidationSeverity.ERROR) {
36✔
1700
            return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InstructionJSONInSingleQuotes(), ValidationCode.JSON_IN_SINGLE_QUOTES);
6✔
1701
        } else if (severity === ValidationSeverity.WARNING) {
30✔
1702
            return Validator.createWarning(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InstructionJSONInSingleQuotes(), ValidationCode.JSON_IN_SINGLE_QUOTES);
24✔
1703
        }
1704
        return null;
6✔
1705
    }
1706

1707
    private static createEmptyContinuationLine(start: Position, end: Position, severity: ValidationSeverity | undefined): Diagnostic | null {
1708
        if (severity === ValidationSeverity.ERROR) {
523✔
1709
            return Validator.createError(null, start, end, Validator.getDiagnosticMessage_EmptyContinuationLine(), ValidationCode.EMPTY_CONTINUATION_LINE);
7✔
1710
        } else if (severity === ValidationSeverity.WARNING) {
516✔
1711
            return Validator.createWarning(null, start, end, Validator.getDiagnosticMessage_EmptyContinuationLine(), ValidationCode.EMPTY_CONTINUATION_LINE);
6✔
1712
        }
1713
        return null;
510✔
1714
    }
1715

1716
    private createMultipleInstructions(instructionLine: uinteger | null, range: Range, severity: ValidationSeverity | undefined, instruction: string): Diagnostic | null {
1717
        if (severity === ValidationSeverity.ERROR) {
40✔
1718
            return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InstructionMultiple(instruction), ValidationCode.MULTIPLE_INSTRUCTIONS, [DiagnosticTag.Unnecessary]);
12✔
1719
        } else if (severity === ValidationSeverity.WARNING) {
28✔
1720
            return Validator.createWarning(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_InstructionMultiple(instruction), ValidationCode.MULTIPLE_INSTRUCTIONS, [DiagnosticTag.Unnecessary]);
24✔
1721
        }
1722
        return null;
4✔
1723
    }
1724

1725
    private createLowercaseDirective(start: Position, end: Position): Diagnostic | null {
1726
        if (this.settings.directiveCasing === ValidationSeverity.ERROR) {
6✔
1727
            return Validator.createError(null, start, end, Validator.getDiagnosticMessage_DirectiveCasing(), ValidationCode.CASING_DIRECTIVE);
1✔
1728
        } else if (this.settings.directiveCasing === ValidationSeverity.WARNING) {
5✔
1729
            return Validator.createWarning(null, start, end, Validator.getDiagnosticMessage_DirectiveCasing(), ValidationCode.CASING_DIRECTIVE);
4✔
1730
        }
1731
        return null;
1✔
1732
    }
1733

1734
    createUppercaseInstruction(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic | null {
1735
        if (this.settings.instructionCasing === ValidationSeverity.ERROR) {
85✔
1736
            return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionCasing(), ValidationCode.CASING_INSTRUCTION);
2✔
1737
        } else if (this.settings.instructionCasing === ValidationSeverity.WARNING) {
83✔
1738
            return Validator.createWarning(instructionLine, start, end, Validator.getDiagnosticMessage_InstructionCasing(), ValidationCode.CASING_INSTRUCTION);
78✔
1739
        }
1740
        return null;
5✔
1741
    }
1742

1743
    createMaintainerDeprecated(instructionLine: uinteger | null, start: Position, end: Position): Diagnostic | null {
1744
        if (this.settings.deprecatedMaintainer === ValidationSeverity.ERROR) {
82✔
1745
            return Validator.createError(instructionLine, start, end, Validator.getDiagnosticMessage_DeprecatedMaintainer(), ValidationCode.DEPRECATED_MAINTAINER, [DiagnosticTag.Deprecated]);
2✔
1746
        } else if (this.settings.deprecatedMaintainer === ValidationSeverity.WARNING) {
80✔
1747
            return Validator.createWarning(instructionLine, start, end, Validator.getDiagnosticMessage_DeprecatedMaintainer(), ValidationCode.DEPRECATED_MAINTAINER, [DiagnosticTag.Deprecated]);
4✔
1748
        }
1749
        return null;
76✔
1750
    }
1751

1752
    private createWORKDIRNotAbsolute(instructionLine: uinteger | null,range: Range): Diagnostic | null {
1753
        if (this.settings.instructionWorkdirRelative === ValidationSeverity.ERROR) {
32✔
1754
            return Validator.createError(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_WORKDIRPathNotAbsolute(), ValidationCode.WORKDIR_IS_NOT_ABSOLUTE);
8✔
1755
        } else if (this.settings.instructionWorkdirRelative === ValidationSeverity.WARNING) {
24✔
1756
            return Validator.createWarning(instructionLine, range.start, range.end, Validator.getDiagnosticMessage_WORKDIRPathNotAbsolute(), ValidationCode.WORKDIR_IS_NOT_ABSOLUTE);
16✔
1757
        }
1758
        return null;
8✔
1759
    }
1760

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

1765
    static createDiagnostic(severity: DiagnosticSeverity, instructionLine: uinteger | null, start: Position, end: Position, description: string, code?: ValidationCode, tags?: DiagnosticTag[]): DockerfileDiagnostic {
1766
        return {
1,481✔
1767
            instructionLine: instructionLine,
1768
            range: {
1769
                start: start,
1770
                end: end
1771
            },
1772
            message: description,
1773
            severity: severity,
1774
            code: code,
1775
            tags: tags,
1776
            source: "dockerfile-utils"
1777
        };
1778
    }
1779
}
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