diff --git a/corpus/annotation.txt b/corpus/annotation.txt index 55c44cf..2aca3ba 100644 --- a/corpus/annotation.txt +++ b/corpus/annotation.txt @@ -2,136 +2,84 @@ empty nix-shell ================== -#!/usr/bin/env nix-shell #!nix-shell --- (source_file - (first_line) - (next_line - (annotation_line))) + (nix_shell_directive)) ================== -whitespace in nix-shell +nix-shell no interpreter ================== -#!/usr/bin/env nix-shell -#! nix-shell +#!nix-shell -p gnused --- (source_file - (first_line) - (next_line - (annotation_line))) + (nix_shell_directive)) ================== minimal nix-shell invocation with interpreter ================== -#!/usr/bin/env nix-shell #!nix-shell -i bash --- (source_file - (first_line) - (next_line - (annotation_line - (nix_shell_args - (interpreter - (sh_arg)))))) + (nix_shell_directive + (interpreter))) + +================== +whitespace in nix-shell +================== + +#! nix-shell -i bash +--- + +(source_file + (nix_shell_directive + (interpreter))) ================== non-interpreter argument ================== -#!/usr/bin/env nix-shell #!nix-shell -i bash -p gnused --- (source_file - (first_line) - (next_line - (annotation_line - (nix_shell_args - (interpreter - (sh_arg)) - (sh_arg) - (sh_arg))))) + (nix_shell_directive + (interpreter))) ================== non-interpreter flags ================== -#!/usr/bin/env nix-shell #!nix-shell -i bash -p gnused awk --- (source_file - (first_line) - (next_line - (annotation_line - (nix_shell_args - (interpreter - (sh_arg)) - (sh_arg) - (sh_arg) - (sh_arg))))) + (nix_shell_directive + (interpreter))) ================== quoted arguments ================== -#!/usr/bin/env nix-shell #!nix-shell -i bash arg\ one 'arg"Two' "'argThree" --- (source_file - (first_line) - (next_line - (annotation_line - (nix_shell_args - (interpreter - (sh_arg)) - (sh_arg) - (sh_arg) - (sh_arg))))) + (nix_shell_directive + (interpreter))) ================== order-independent ================== -#!/usr/bin/env nix-shell #!nix-shell -p gnused -i bash --- (source_file - (first_line) - (next_line - (annotation_line - (nix_shell_args - (sh_arg) - (sh_arg) - (interpreter - (sh_arg)))))) - -================== -multiple annotation lines -================== - -#!/usr/bin/env nix-shell -#!nix-shell -p gnused -#!nix-shell -i bash ---- - -(source_file - (first_line) - (next_line - (annotation_line - (nix_shell_args - (sh_arg) - (sh_arg)))) - (next_line - (annotation_line - (nix_shell_args - (interpreter - (sh_arg)))))) + (nix_shell_directive + (interpreter))) diff --git a/corpus/payload.txt b/corpus/payload.txt index eeaf496..87edc00 100644 --- a/corpus/payload.txt +++ b/corpus/payload.txt @@ -1,3 +1,11 @@ +================== +empty +================== + +--- + +(source_file) + ================== nix-shell with a bash payload ================== @@ -8,13 +16,8 @@ echo "hello, world!" --- (source_file - (first_line) - (next_line - (annotation_line - (nix_shell_args - (interpreter - (sh_arg))))) - (next_line)) + (nix_shell_directive + (interpreter))) ================== nix-shell with interspersed payload @@ -28,12 +31,39 @@ echo everybody --- (source_file - (first_line) - (next_line) - (next_line) - (next_line - (annotation_line - (nix_shell_args - (interpreter - (sh_arg))))) - (next_line)) + (nix_shell_directive + (interpreter))) + +================== +nix-shell with multiple directives +================== + +#!/usr/bin/env nix-shell +echo hello +#!nix-shell -p gnused +#!nix-shell -i bash +echo again +#!nix-shell +echo everybody +--- + +(source_file + (nix_shell_directive) + (nix_shell_directive + (interpreter)) + (nix_shell_directive)) + +================== +nix-shell with conflicting interpreters (last wins, but good luck with that) +================== + +#!/usr/bin/env nix-shell +#!nix-shell -i bash +#!nix-shell -i python3 +--- + +(source_file + (nix_shell_directive + (interpreter)) + (nix_shell_directive + (interpreter))) diff --git a/corpus/shebang.txt b/corpus/shebang.txt index 8791c6b..ca3c137 100644 --- a/corpus/shebang.txt +++ b/corpus/shebang.txt @@ -5,8 +5,7 @@ nix-shell shebang without any interpreter option #!/usr/bin/env nix-shell --- -(source_file - (first_line)) +(source_file) ================== nix-shell shebang whitespace @@ -15,8 +14,7 @@ nix-shell shebang whitespace #! /usr/bin/env nix-shell --- -(source_file - (first_line)) +(source_file) ================== nix-shell shebang tab @@ -25,8 +23,7 @@ nix-shell shebang tab #! /usr/bin/env nix-shell --- -(source_file - (first_line)) +(source_file) ================== nix-shell shebang tabs and spaces @@ -35,28 +32,7 @@ nix-shell shebang tabs and spaces #! /usr/bin/env nix-shell --- -(source_file - (first_line)) - -================== -nix-shell shebang no env -================== - -#!nix-shell ---- - -(source_file - (first_line)) - -================== -nix-shell shebang whitespace no env -================== - -#! nix-shell ---- - -(source_file - (first_line)) +(source_file) ================== nix-shell shebang via relative env @@ -65,8 +41,7 @@ nix-shell shebang via relative env #!env nix-shell --- -(source_file - (first_line)) +(source_file) ================== nix-shell shebang via /bin/env @@ -75,5 +50,14 @@ nix-shell shebang via /bin/env #!/bin/env nix-shell --- -(source_file - (first_line)) +(source_file) + +================== +nix-shell multiple shebangs +================== + +#!/usr/bin/env nix-shell +#!/usr/bin/env nix-shell +--- + +(source_file) diff --git a/grammar.js b/grammar.js index 27ddffa..3d58a9f 100644 --- a/grammar.js +++ b/grammar.js @@ -2,26 +2,29 @@ module.exports = grammar({ name: 'nix_shell', rules: { - // TODO: allow trailing whitespace on any line - source_file: $ => seq($.first_line, repeat($.next_line)), - - first_line: $ => seq($._shebang_open, $._opt_ws, optional(seq($._env, $._ws)), $._nix_shell), - next_line: $ => seq($._newline, choice( - $.annotation_line, - repeat(/./), - )), - annotation_line: $ => seq($._shebang_open, $._opt_ws, $._nix_shell, optional($.nix_shell_args)), + source_file: $ => seq( + optional($._first_line), + repeat($._next_line), + ), + _first_line: $ => choice( + $.nix_shell_directive, + repeat1(/./), + ), + _next_line: $ => seq('\n', $._first_line), + nix_shell_directive: $ => seq( + /#![ \t]*nix-shell/, + repeat(seq($._ws, optional($._nix_shell_arg))), + ), // nix acts as if it actually just `exec`'s whichever line contains `#! nix-shell` in the style of `sh`'s `exec`. // so arguments can be quoted, invoke subshells, interpolate environment variables, etc. // the full scope cannot be covered, but the minimal scope here gets 99% of use cases. - nix_shell_args: $ => repeat1(seq($._ws, $._nix_shell_arg)), _nix_shell_arg: $ => choice( seq('-i', $._opt_ws, $.interpreter), - $.sh_arg, + $._sh_arg, ), - interpreter: $ => $.sh_arg, - sh_arg: $ => choice( + interpreter: $ => $._sh_arg, + _sh_arg: $ => choice( $._sh_lit, $._sh_quote1, $._sh_quote2, @@ -47,20 +50,12 @@ module.exports = grammar({ '"', ), - _shebang_open: $ => '#!', - // TODO: env accepts flags like `-v` or `--unset=NAME` or `VAR=VALUE` before `nix-shell`, - _env: $ => choice( - '/usr/bin/env', - '/bin/env', - 'env', - ), - _nix_shell: $ => 'nix-shell', // N.B.: this accepts more than it needs to: - // - shebang parser allows tab characters // - #!nix-shell directives do not support tabs - // - wherever #!nix-shell directives allow whitespace, it's a *single* space -- never multiple + // - some places where whitespace is accepted (e.g. `#! nix-shell`), only a *single* space is accepted. + // it's not clear how much of this is intentional, v.s. oversight in matching broader shell parsing, + // so err on overaccepting in case future nix-shell supports these. _ws: $ => /[ \t]+/, _opt_ws: $ => /[ \t]*/, - _newline: $ => '\n', } }); diff --git a/queries/injections.scm b/queries/injections.scm new file mode 100644 index 0000000..5844683 --- /dev/null +++ b/queries/injections.scm @@ -0,0 +1,8 @@ +; if we find an interpreter, mark the *entire file* as injected content. +; nix-shell doesn't process the source file before sending it to the interpreter, +; so nix-shell directives like `#!nix-shell -i bash` are both a directive to nix-shell +; and actual language content handled by the specified interpreter. +((source_file + (nix_shell_directive + (interpreter) @injection.language)) @injection.content + (#set! injection.include-children))