Repository: apple/pkl (2025-07-16T08:05:55Z)

Issue:
shebang comment parsing is too lenient
The shebang comment should only be valid to start a file, but currently is accepted in any position.

This is okay:

```pkl
#!/usr/bin/env pkl eval

foo = 1
```

This should error:

```pkl
foo = 1

#!/usr/bin/env pkl eval
```


Discussion:

Patch:
diff --git a/pkl-parser/src/main/java/org/pkl/parser/Parser.java b/pkl-parser/src/main/java/org/pkl/parser/Parser.java
index a69721a1d..52e4154a8 100644
--- a/pkl-parser/src/main/java/org/pkl/parser/Parser.java
+++ b/pkl-parser/src/main/java/org/pkl/parser/Parser.java
@@ -18,6 +18,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.EnumSet;
 import java.util.List;
 import java.util.function.Supplier;
 import org.pkl.parser.syntax.Annotation;
@@ -112,6 +113,7 @@ public Module parseModule(String source) {
     if (lookahead == Token.EOF) {
       return new Module(Collections.singletonList(null), new Span(0, 0));
     }
+    if (lookahead == Token.SHEBANG) next();
     var start = spanLookahead;
     Span end = null;
     ModuleDecl moduleDecl;
@@ -1787,16 +1789,16 @@ private FullToken next() {
   private FullToken forceNext() {
     var tk = lexer.next();
     precededBySemicolon = false;
-    while (tk == Token.LINE_COMMENT
-        || tk == Token.BLOCK_COMMENT
-        || tk == Token.SEMICOLON
-        || tk == Token.SHEBANG) {
+    while (AFFIXES.contains(tk)) {
       precededBySemicolon = precededBySemicolon || tk == Token.SEMICOLON;
       tk = lexer.next();
     }
     return new FullToken(tk, lexer.span(), lexer.newLinesBetween);
   }
 
+  private static final EnumSet<Token> AFFIXES =
+      EnumSet.of(Token.LINE_COMMENT, Token.BLOCK_COMMENT, Token.SEMICOLON);
+
   // Like next, but don't ignore comments
   private FullToken nextComment() {
     prev = _lookahead;
diff --git a/pkl-parser/src/main/resources/org/pkl/parser/errorMessages.properties b/pkl-parser/src/main/resources/org/pkl/parser/errorMessages.properties
index 3eeb40a07..bb5776577 100644
--- a/pkl-parser/src/main/resources/org/pkl/parser/errorMessages.properties
+++ b/pkl-parser/src/main/resources/org/pkl/parser/errorMessages.properties
@@ -95,3 +95,6 @@ danglingDocComment=\
 Dangling documentation comment.\n\
 \n\
 Documentation comments must be attached to modules, classes, typealiases, methods, or properties.
+
+wrongShebangPosition=\
+Shebangs are only allowed at the very beginning of a file.


Test patch:
diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/shebang.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/shebang.pkl
new file mode 100644
index 000000000..79658f0b9
--- /dev/null
+++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/shebang.pkl
@@ -0,0 +1,2 @@
+foo = 1
+#!/usr/bin/env pkl eval
diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/shebang.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/shebang.pkl
new file mode 100644
index 000000000..76b2f22db
--- /dev/null
+++ b/pkl-core/src/test/files/LanguageSnippetTests/input/syntax/shebang.pkl
@@ -0,0 +1,2 @@
+#!/usr/bin/env pkl eval
+foo = 1
diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/shebang.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/shebang.err
new file mode 100644
index 000000000..f721d0a99
--- /dev/null
+++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/shebang.err
@@ -0,0 +1,6 @@
+–– Pkl Error ––
+Invalid token at position. Expected a class, typealias, method, or property.
+
+x | #!/usr/bin/env pkl eval
+    ^^^^^^^^^^^^^^^^^^^^^^^
+at shebang (file:///$snippetsDir/input/errors/shebang.pkl)
diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/shebang.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/shebang.pcf
new file mode 100644
index 000000000..c4e5bcc80
--- /dev/null
+++ b/pkl-core/src/test/files/LanguageSnippetTests/output/syntax/shebang.pcf
@@ -0,0 +1,1 @@
+foo = 1