diff --git a/components/prism-docker.js b/components/prism-docker.js index 270e66c554..2c8c7740aa 100644 --- a/components/prism-docker.js +++ b/components/prism-docker.js @@ -1,14 +1,98 @@ -Prism.languages.docker = { - 'keyword': { - pattern: /(^\s*)(?:ADD|ARG|CMD|COPY|ENTRYPOINT|ENV|EXPOSE|FROM|HEALTHCHECK|LABEL|MAINTAINER|ONBUILD|RUN|SHELL|STOPSIGNAL|USER|VOLUME|WORKDIR)(?=\s)/mi, - lookbehind: true - }, - 'string': /("|')(?:(?!\1)[^\\\r\n]|\\(?:\r\n|[\s\S]))*\1/, - 'comment': { - pattern: /#.*/, +(function (Prism) { + + // Many of the following regexes will contain negated lookaheads like `[ \t]+(?![ \t])`. This is a trick to ensure + // that quantifiers behave *atomically*. Atomic quantifiers are necessary to prevent exponential backtracking. + + var spaceAfterBackSlash = /\\[\r\n](?:\s|\\[\r\n]|#.*(?!.))*(?![\s#]|\\[\r\n])/.source; + // At least one space, comment, or line break + var space = /(?:[ \t]+(?![ \t])(?:)?|)/.source + .replace(//g, function () { return spaceAfterBackSlash; }); + + var string = /"(?:[^"\\\r\n]|\\(?:\r\n|[\s\S]))*"|'(?:[^'\\\r\n]|\\(?:\r\n|[\s\S]))*'/.source; + var option = /--[\w-]+=(?:|(?!["'])(?:[^\s\\]|\\.)+)/.source.replace(//g, function () { return string; }); + + var stringRule = { + pattern: RegExp(string), + greedy: true + }; + var commentRule = { + pattern: /(^[ \t]*)#.*/m, + lookbehind: true, greedy: true - }, - 'punctuation': /---|\.\.\.|[:[\]{}\-,|>?]/ -}; + }; + + /** + * @param {string} source + * @param {string} flags + * @returns {RegExp} + */ + function re(source, flags) { + source = source + .replace(//g, function () { return option; }) + .replace(//g, function () { return space; }); + + return RegExp(source, flags); + } + + Prism.languages.docker = { + 'instruction': { + pattern: /(^[ \t]*)(?:ADD|ARG|CMD|COPY|ENTRYPOINT|ENV|EXPOSE|FROM|HEALTHCHECK|LABEL|MAINTAINER|ONBUILD|RUN|SHELL|STOPSIGNAL|USER|VOLUME|WORKDIR)(?=\s)(?:\\.|[^\r\n\\])*(?:\\$(?:\s|#.*$)*(?![\s#])(?:\\.|[^\r\n\\])*)*/mi, + lookbehind: true, + greedy: true, + inside: { + 'options': { + pattern: re(/(^(?:ONBUILD)?\w+)(?:)*/.source, 'i'), + lookbehind: true, + greedy: true, + inside: { + 'property': { + pattern: /(^|\s)--[\w-]+/, + lookbehind: true + }, + 'string': [ + stringRule, + { + pattern: /(=)(?!["'])(?:[^\s\\]|\\.)+/, + lookbehind: true + } + ], + 'operator': /\\$/m, + 'punctuation': /=/ + } + }, + 'keyword': [ + { + // https://docs.docker.com/engine/reference/builder/#healthcheck + pattern: re(/(^(?:ONBUILD)?HEALTHCHECK(?:)*)(?:CMD|NONE)\b/.source, 'i'), + lookbehind: true, + greedy: true + }, + { + // https://docs.docker.com/engine/reference/builder/#from + pattern: re(/(^(?:ONBUILD)?FROM(?:)*(?!--)[^ \t\\]+)AS/.source, 'i'), + lookbehind: true, + greedy: true + }, + { + // https://docs.docker.com/engine/reference/builder/#onbuild + pattern: re(/(^ONBUILD)\w+/.source, 'i'), + lookbehind: true, + greedy: true + }, + { + pattern: /^\w+/, + greedy: true + } + ], + 'comment': commentRule, + 'string': stringRule, + 'variable': /\$(?:\w+|\{[^{}"'\\]*\})/, + 'operator': /\\$/m + } + }, + 'comment': commentRule + }; + + Prism.languages.dockerfile = Prism.languages.docker; -Prism.languages.dockerfile = Prism.languages.docker; +}(Prism)); diff --git a/components/prism-docker.min.js b/components/prism-docker.min.js index 9c1b73ff6d..3d23e8a719 100644 --- a/components/prism-docker.min.js +++ b/components/prism-docker.min.js @@ -1 +1 @@ -Prism.languages.docker={keyword:{pattern:/(^\s*)(?:ADD|ARG|CMD|COPY|ENTRYPOINT|ENV|EXPOSE|FROM|HEALTHCHECK|LABEL|MAINTAINER|ONBUILD|RUN|SHELL|STOPSIGNAL|USER|VOLUME|WORKDIR)(?=\s)/im,lookbehind:!0},string:/("|')(?:(?!\1)[^\\\r\n]|\\(?:\r\n|[\s\S]))*\1/,comment:{pattern:/#.*/,greedy:!0},punctuation:/---|\.\.\.|[:[\]{}\-,|>?]/},Prism.languages.dockerfile=Prism.languages.docker; \ No newline at end of file +!function(e){var r="(?:[ \t]+(?![ \t])(?:)?|)".replace(//g,function(){return"\\\\[\r\n](?:\\s|\\\\[\r\n]|#.*(?!.))*(?![\\s#]|\\\\[\r\n])"}),n="\"(?:[^\"\\\\\r\n]|\\\\(?:\r\n|[^]))*\"|'(?:[^'\\\\\r\n]|\\\\(?:\r\n|[^]))*'",t="--[\\w-]+=(?:|(?![\"'])(?:[^\\s\\\\]|\\\\.)+)".replace(//g,function(){return n}),o={pattern:RegExp(n),greedy:!0},i={pattern:/(^[ \t]*)#.*/m,lookbehind:!0,greedy:!0};function a(e,n){return e=e.replace(//g,function(){return t}).replace(//g,function(){return r}),RegExp(e,n)}e.languages.docker={instruction:{pattern:/(^[ \t]*)(?:ADD|ARG|CMD|COPY|ENTRYPOINT|ENV|EXPOSE|FROM|HEALTHCHECK|LABEL|MAINTAINER|ONBUILD|RUN|SHELL|STOPSIGNAL|USER|VOLUME|WORKDIR)(?=\s)(?:\\.|[^\r\n\\])*(?:\\$(?:\s|#.*$)*(?![\s#])(?:\\.|[^\r\n\\])*)*/im,lookbehind:!0,greedy:!0,inside:{options:{pattern:a("(^(?:ONBUILD)?\\w+)(?:)*","i"),lookbehind:!0,greedy:!0,inside:{property:{pattern:/(^|\s)--[\w-]+/,lookbehind:!0},string:[o,{pattern:/(=)(?!["'])(?:[^\s\\]|\\.)+/,lookbehind:!0}],operator:/\\$/m,punctuation:/=/}},keyword:[{pattern:a("(^(?:ONBUILD)?HEALTHCHECK(?:)*)(?:CMD|NONE)\\b","i"),lookbehind:!0,greedy:!0},{pattern:a("(^(?:ONBUILD)?FROM(?:)*(?!--)[^ \t\\\\]+)AS","i"),lookbehind:!0,greedy:!0},{pattern:a("(^ONBUILD)\\w+","i"),lookbehind:!0,greedy:!0},{pattern:/^\w+/,greedy:!0}],comment:i,string:o,variable:/\$(?:\w+|\{[^{}"'\\]*\})/,operator:/\\$/m}},comment:i},e.languages.dockerfile=e.languages.docker}(Prism); \ No newline at end of file diff --git a/tests/languages/docker/instruction_feature.test b/tests/languages/docker/instruction_feature.test new file mode 100644 index 0000000000..49179c194b --- /dev/null +++ b/tests/languages/docker/instruction_feature.test @@ -0,0 +1,186 @@ +RUN apt-get \ +update && apt-get \ +#comment +# +\ +\ + + +install git -y #not-a-comment \ +something + +RUN echo hello \ +# comment +world + + # this is a comment-line + RUN echo hello +RUN echo world + +RUN echo "\ + hello\ + world" + +LABEL multi.label1="value1" \ + multi.label2="value2" \ + other="value3" + +EXPOSE 80/udp + +ENV MY_NAME="John Doe" +ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \ + MY_CAT=fluffy + +ADD hom?.txt /mydir/ + +ENTRYPOINT ["executable", "param1", "param2"] + +FROM debian:stable +RUN apt-get update && apt-get install -y --force-yes apache2 +EXPOSE 80 443 +VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"] +ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"] + +ENTRYPOINT [ "/path/myprocess", \ + "arg1", \ + "arg2" \ +] + +---------------------------------------------------- + +[ + ["instruction", [ + ["keyword", "RUN"], " apt-get ", ["operator", "\\"], + "\r\nupdate && apt-get ", ["operator", "\\"], + ["comment", "#comment"], + ["comment", "#"], + ["operator", "\\"], + ["operator", "\\"], + + "\r\n\r\n\r\ninstall git -y #not-a-comment ", ["operator", "\\"], + "\r\nsomething" + ]], + + ["instruction", [ + ["keyword", "RUN"], " echo hello ", ["operator", "\\"], + ["comment", "# comment"], + "\r\nworld" + ]], + + ["comment", "# this is a comment-line"], + ["instruction", [ + ["keyword", "RUN"], + " echo hello" + ]], + ["instruction", [ + ["keyword", "RUN"], + " echo world" + ]], + + ["instruction", [ + ["keyword", "RUN"], + " echo ", + ["string", "\"\\\r\n hello\\\r\n world\""] + ]], + + ["instruction", [ + ["keyword", "LABEL"], + " multi.label1=", + ["string", "\"value1\""], + ["operator", "\\"], + + "\r\n multi.label2=", + ["string", "\"value2\""], + ["operator", "\\"], + + "\r\n other=", + ["string", "\"value3\""] + ]], + + ["instruction", [ + ["keyword", "EXPOSE"], + " 80/udp" + ]], + + ["instruction", [ + ["keyword", "ENV"], + " MY_NAME=", + ["string", "\"John Doe\""] + ]], + + ["instruction", [ + ["keyword", "ENV"], + " MY_NAME=", + ["string", "\"John Doe\""], + " MY_DOG=Rex\\ The\\ Dog ", + ["operator", "\\"], + + "\r\n MY_CAT=fluffy" + ]], + + ["instruction", [ + ["keyword", "ADD"], + " hom?.txt /mydir/" + ]], + + ["instruction", [ + ["keyword", "ENTRYPOINT"], + " [", + ["string", "\"executable\""], + ", ", + ["string", "\"param1\""], + ", ", + ["string", "\"param2\""], + "]" + ]], + + ["instruction", [ + ["keyword", "FROM"], + " debian:stable" + ]], + ["instruction", [ + ["keyword", "RUN"], + " apt-get update && apt-get install -y --force-yes apache2" + ]], + ["instruction", [ + ["keyword", "EXPOSE"], + " 80 443" + ]], + ["instruction", [ + ["keyword", "VOLUME"], + " [", + ["string", "\"/var/www\""], + ", ", + ["string", "\"/var/log/apache2\""], + ", ", + ["string", "\"/etc/apache2\""], + "]" + ]], + ["instruction", [ + ["keyword", "ENTRYPOINT"], + " [", + ["string", "\"/usr/sbin/apache2ctl\""], + ", ", + ["string", "\"-D\""], + ", ", + ["string", "\"FOREGROUND\""], + "]" + ]], + + ["instruction", [ + ["keyword", "ENTRYPOINT"], + " [ ", + ["string", "\"/path/myprocess\""], + ", ", + ["operator", "\\"], + + ["string", "\"arg1\""], + ", ", + ["operator", "\\"], + + ["string", "\"arg2\""], + ["operator", "\\"], + + "\r\n]" + ]] +] \ No newline at end of file diff --git a/tests/languages/docker/keyword_feature.test b/tests/languages/docker/keyword_feature.test index a36a7e00b6..5330a6221e 100644 --- a/tests/languages/docker/keyword_feature.test +++ b/tests/languages/docker/keyword_feature.test @@ -1,5 +1,6 @@ ONBUILD ADD . /app/src FROM ubuntu +FROM ubuntu AS build MAINTAINER SvenDowideit@home.org.au RUN cd /tmp EXPOSE 5900 @@ -9,6 +10,7 @@ VOLUME /myvol USER daemon WORKDIR /a HEALTHCHECK CMD echo "foo" +HEALTHCHECK NONE LABEL version="1.0" ENTRYPOINT ["top", "-b"] ARG user1 @@ -18,28 +20,94 @@ STOPSIGNAL signal ---------------------------------------------------- [ - ["keyword", "ONBUILD"], ["keyword", "ADD"], " . /app/src\r\n", - ["keyword", "FROM"], " ubuntu\r\n", - ["keyword", "MAINTAINER"], " SvenDowideit@home.org.au\r\n", - ["keyword", "RUN"], " cd /tmp\r\n", - ["keyword", "EXPOSE"], " 5900\r\n", - ["keyword", "ENV"], " myName John Doe\r\n", - ["keyword", "COPY"], " hom* /mydir/\r\n", - ["keyword", "VOLUME"], " /myvol\r\n", - ["keyword", "USER"], " daemon\r\n", - ["keyword", "WORKDIR"], " /a\r\n", - ["keyword", "HEALTHCHECK"], ["keyword", "CMD"], " echo ", ["string", "\"foo\""], - ["keyword", "LABEL"], " version=", ["string", "\"1.0\""], - ["keyword", "ENTRYPOINT"], - ["punctuation", "["], ["string", "\"top\""], ["punctuation", ","], - ["string", "\"-b\""], ["punctuation", "]"], - ["keyword", "ARG"], " user1\r\n", - ["keyword", "SHELL"], - ["punctuation", "["], ["string", "\"powershell\""], ["punctuation", ","], - ["string", "\"-command\""], ["punctuation", "]"], - ["keyword", "STOPSIGNAL"], " signal" + ["instruction", [ + ["keyword", "ONBUILD"], + ["keyword", "ADD"], + " . /app/src" + ]], + ["instruction", [ + ["keyword", "FROM"], + " ubuntu" + ]], + ["instruction", [ + ["keyword", "FROM"], + " ubuntu ", + ["keyword", "AS"], + " build" + ]], + ["instruction", [ + ["keyword", "MAINTAINER"], + " SvenDowideit@home.org.au" + ]], + ["instruction", [ + ["keyword", "RUN"], + " cd /tmp" + ]], + ["instruction", [ + ["keyword", "EXPOSE"], + " 5900" + ]], + ["instruction", [ + ["keyword", "ENV"], + " myName John Doe" + ]], + ["instruction", [ + ["keyword", "COPY"], + " hom* /mydir/" + ]], + ["instruction", [ + ["keyword", "VOLUME"], + " /myvol" + ]], + ["instruction", [ + ["keyword", "USER"], + " daemon" + ]], + ["instruction", [ + ["keyword", "WORKDIR"], + " /a" + ]], + ["instruction", [ + ["keyword", "HEALTHCHECK"], + ["keyword", "CMD"], + " echo ", + ["string", "\"foo\""] + ]], + ["instruction", [ + ["keyword", "HEALTHCHECK"], + ["keyword", "NONE"] + ]], + ["instruction", [ + ["keyword", "LABEL"], + " version=", + ["string", "\"1.0\""] + ]], + ["instruction", [ + ["keyword", "ENTRYPOINT"], + " [", + ["string", "\"top\""], + ", ", + ["string", "\"-b\""], + "]" + ]], + ["instruction", [ + ["keyword", "ARG"], + " user1" + ]], + ["instruction", [ + ["keyword", "SHELL"], + " [", + ["string", "\"powershell\""], + ", ", + ["string", "\"-command\""], + "]" + ]], + ["instruction", [ + ["keyword", "STOPSIGNAL"], + " signal" + ]] ] ---------------------------------------------------- -Checks for keywords. \ No newline at end of file +Checks for keywords. diff --git a/tests/languages/docker/options_feature.test b/tests/languages/docker/options_feature.test new file mode 100644 index 0000000000..03f4841ce5 --- /dev/null +++ b/tests/languages/docker/options_feature.test @@ -0,0 +1,89 @@ +ADD --chown=1 files* /somedir/ +COPY --chown=1 files* /somedir/ + +HEALTHCHECK --interval=5m --timeout=3s \ + CMD foo + +ONBUILD HEALTHCHECK --interval=5m --timeout=3s \ + CMD foo + +HEALTHCHECK \ + --interval=5m \ + --timeout=3s \ + CMD foo + +---------------------------------------------------- + +[ + ["instruction", [ + ["keyword", "ADD"], + ["options", [ + ["property", "--chown"], + ["punctuation", "="], + ["string", "1"] + ]], + " files* /somedir/" + ]], + ["instruction", [ + ["keyword", "COPY"], + ["options", [ + ["property", "--chown"], + ["punctuation", "="], + ["string", "1"] + ]], + " files* /somedir/" + ]], + + ["instruction", [ + ["keyword", "HEALTHCHECK"], + ["options", [ + ["property", "--interval"], + ["punctuation", "="], + ["string", "5m"], + ["property", "--timeout"], + ["punctuation", "="], + ["string", "3s"] + ]], + ["operator", "\\"], + + ["keyword", "CMD"], + " foo" + ]], + + ["instruction", [ + ["keyword", "ONBUILD"], + ["keyword", "HEALTHCHECK"], + ["options", [ + ["property", "--interval"], + ["punctuation", "="], + ["string", "5m"], + ["property", "--timeout"], + ["punctuation", "="], + ["string", "3s"] + ]], + ["operator", "\\"], + + ["keyword", "CMD"], + " foo" + ]], + + ["instruction", [ + ["keyword", "HEALTHCHECK"], + ["operator", "\\"], + + ["options", [ + ["property", "--interval"], + ["punctuation", "="], + ["string", "5m"], + ["operator", "\\"], + + ["property", "--timeout"], + ["punctuation", "="], + ["string", "3s"] + ]], + ["operator", "\\"], + + ["keyword", "CMD"], + " foo" + ]] +] \ No newline at end of file diff --git a/tests/languages/docker/string_feature.test b/tests/languages/docker/string_feature.test index e131f02136..9f7b812f4c 100644 --- a/tests/languages/docker/string_feature.test +++ b/tests/languages/docker/string_feature.test @@ -1,23 +1,49 @@ -"" -"fo\"obar" -"foo\ +RUN echo "" +RUN echo "fo\"obar" +RUN echo "foo\ bar" -'' -'fo\'obar' -'foo\ + +RUN echo '' +RUN echo 'fo\'obar' +RUN echo 'foo\ bar' ---------------------------------------------------- [ - ["string", "\"\""], - ["string", "\"fo\\\"obar\""], - ["string", "\"foo\\\r\nbar\""], - ["string", "''"], - ["string", "'fo\\'obar'"], - ["string", "'foo\\\r\nbar'"] + ["instruction", [ + ["keyword", "RUN"], + " echo ", + ["string", "\"\""] + ]], + ["instruction", [ + ["keyword", "RUN"], + " echo ", + ["string", "\"fo\\\"obar\""] + ]], + ["instruction", [ + ["keyword", "RUN"], + " echo ", + ["string", "\"foo\\\r\nbar\""] + ]], + + ["instruction", [ + ["keyword", "RUN"], + " echo ", + ["string", "''"] + ]], + ["instruction", [ + ["keyword", "RUN"], + " echo ", + ["string", "'fo\\'obar'"] + ]], + ["instruction", [ + ["keyword", "RUN"], + " echo ", + ["string", "'foo\\\r\nbar'"] + ]] ] ---------------------------------------------------- -Checks for strings. \ No newline at end of file +Checks for strings. diff --git a/tests/languages/docker/variable_feature.test b/tests/languages/docker/variable_feature.test new file mode 100644 index 0000000000..53067767e9 --- /dev/null +++ b/tests/languages/docker/variable_feature.test @@ -0,0 +1,62 @@ +FROM busybox +USER ${user:-some_user} +ARG user +USER $user +RUN echo $CONT_IMG_VER + +ARG CODE_VERSION=latest +FROM base:${CODE_VERSION} +CMD /code/run-app + +FROM extras:${CODE_VERSION} +CMD /code/run-extras + +---------------------------------------------------- + +[ + ["instruction", [ + ["keyword", "FROM"], + " busybox" + ]], + ["instruction", [ + ["keyword", "USER"], + ["variable", "${user:-some_user}"] + ]], + ["instruction", [ + ["keyword", "ARG"], + " user" + ]], + ["instruction", [ + ["keyword", "USER"], + ["variable", "$user"] + ]], + ["instruction", [ + ["keyword", "RUN"], + " echo ", + ["variable", "$CONT_IMG_VER"] + ]], + + ["instruction", [ + ["keyword", "ARG"], + " CODE_VERSION=latest" + ]], + ["instruction", [ + ["keyword", "FROM"], + " base:", + ["variable", "${CODE_VERSION}"] + ]], + ["instruction", [ + ["keyword", "CMD"], + " /code/run-app" + ]], + + ["instruction", [ + ["keyword", "FROM"], + " extras:", + ["variable", "${CODE_VERSION}"] + ]], + ["instruction", [ + ["keyword", "CMD"], + " /code/run-extras" + ]] +] \ No newline at end of file