Skip to content

Commit b2904ba

Browse files
authored
Merge pull request #60 from jansorg/jansorg/path-antiquotations
Support antiquotations in paths
2 parents 3ebf1bd + 425bfd3 commit b2904ba

17 files changed

+350
-46
lines changed

src/main/java/org/nixos/idea/lang/NixParserDefinition.java

+8
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,14 @@ public SpaceRequirements spaceExistenceTypeBetweenTokens(ASTNode left, ASTNode r
7777
if (leftType == NixTypes.DOLLAR && rightType == NixTypes.LCURLY) {
7878
return SpaceRequirements.MUST_NOT;
7979
}
80+
else if (leftType == NixTypes.PATH_SEGMENT) {
81+
// path segment, antiquotation or PATH_END on the right
82+
return SpaceRequirements.MUST_NOT;
83+
}
84+
else if (rightType == NixTypes.PATH_END) {
85+
// path segment or antiquotation on the left
86+
return SpaceRequirements.MUST_NOT;
87+
}
8088
else if (NixTypeUtil.MIGHT_COLLAPSE_WITH_ID.contains(leftType) &&
8189
NixTypeUtil.MIGHT_COLLAPSE_WITH_ID.contains(rightType)) {
8290
return SpaceRequirements.MUST;

src/main/java/org/nixos/idea/lang/highlighter/NixSyntaxHighlighter.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ public class NixSyntaxHighlighter extends SyntaxHighlighterBase {
6666
// Literals
6767
entry(NixTypes.INT, NixTextAttributes.NUMBER),
6868
entry(NixTypes.FLOAT, NixTextAttributes.NUMBER),
69-
entry(NixTypes.PATH, NixTextAttributes.PATH),
70-
entry(NixTypes.HPATH, NixTextAttributes.PATH),
69+
entry(NixTypes.PATH_SEGMENT, NixTextAttributes.PATH),
7170
entry(NixTypes.SPATH, NixTextAttributes.PATH),
7271
entry(NixTypes.URI, NixTextAttributes.URI),
7372
// String literals

src/main/java/org/nixos/idea/psi/NixTypeUtil.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ public final class NixTypeUtil {
2424
NixTypes.ID,
2525
NixTypes.INT,
2626
NixTypes.FLOAT,
27-
NixTypes.PATH,
28-
NixTypes.HPATH,
2927
NixTypes.SPATH,
28+
NixTypes.PATH_SEGMENT,
29+
NixTypes.PATH_END,
3030
NixTypes.URI));
3131

3232
private NixTypeUtil() {} // Cannot be instantiated

src/main/java/org/nixos/idea/psi/impl/NixParserUtil.java

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.intellij.lang.PsiBuilder;
44
import com.intellij.lang.parser.GeneratedParserUtilBase;
55
import com.intellij.openapi.util.Key;
6+
import com.intellij.psi.TokenType;
67
import org.jetbrains.annotations.NotNull;
78

89
public final class NixParserUtil extends GeneratedParserUtilBase {

src/main/lang/Nix.bnf

+3-1
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,9 @@ expr_simple ::=
169169
| list
170170
| legacy_let
171171
identifier ::= ID
172-
literal ::= INT | FLOAT | PATH | HPATH | SPATH | URI
172+
literal ::= INT | FLOAT | URI | path
173+
;{ extends("path")=literal }
174+
path ::= SPATH | PATH_SEGMENT (PATH_SEGMENT | antiquotation)* PATH_END
173175
parens ::= LPAREN expr recover_parens RPAREN { pin=1 }
174176
set ::= [ REC ] LCURLY recover_set (bind recover_set)* RCURLY { pin=2 }
175177
list ::= LBRAC recover_list (expr_select recover_list)* RBRAC { pin=1 }

src/main/lang/Nix.flex

+20-6
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,17 @@ import static org.nixos.idea.psi.NixTypes.*;
4343
%function advance
4444
%type IElementType
4545
%unicode
46-
%state BLOCK STRING IND_STRING ANTIQUOTATION_START ANTIQUOTATION
46+
%state BLOCK STRING IND_STRING ANTIQUOTATION_START ANTIQUOTATION PATH
4747

4848
ANY=[^]
4949
ID=[a-zA-Z_][a-zA-Z0-9_'-]*
5050
INT=[0-9]+
5151
FLOAT=(([1-9][0-9]*\.[0-9]*)|(0?\.[0-9]+))([Ee][+-]?[0-9]+)?
52-
PATH=[a-zA-Z0-9._+-]*(\/[a-zA-Z0-9._+-]+)+\/?
53-
HPATH=\~(\/[a-zA-Z0-9._+-]+)+\/?
54-
SPATH=\<[a-zA-Z0-9._+-]+(\/[a-zA-Z0-9._+-]+)*\>
52+
PATH_CHAR=[a-zA-Z0-9\.\_\-\+]
53+
PATH={PATH_CHAR}*(\/{PATH_CHAR}+)+\/?
54+
PATH_SEG={PATH_CHAR}*\/
55+
HPATH_START=\~\/
56+
SPATH=\<{PATH_CHAR}+(\/{PATH_CHAR}+)*\>
5557
URI=[a-zA-Z][a-zA-Z0-9.+-]*\:[a-zA-Z0-9%/?:@&=+$,\-_.!~*']+
5658

5759
WHITE_SPACE=[\ \t\r\n]+
@@ -91,6 +93,16 @@ MCOMMENT=\/\*([^*]|\*[^\/])*\*\/
9193
"}" { popState(BLOCK); return RCURLY; }
9294
}
9395

96+
<PATH> {
97+
"$"/"{" { pushState(ANTIQUOTATION_START); return DOLLAR; }
98+
{PATH_SEG} { return PATH_SEGMENT; }
99+
{PATH_CHAR}+ { return PATH_SEGMENT; }
100+
// anything else, e.g. whitespace, stops lexing of a PATH
101+
// we're delegating back to the parent state
102+
// PATH_END is an empty-length token to signal the end of the path
103+
[^] { popState(PATH); yypushback(yylength()); return PATH_END; }
104+
}
105+
94106
<YYINITIAL, BLOCK, ANTIQUOTATION> {
95107
"if" { return IF; }
96108
"then" { return THEN; }
@@ -146,8 +158,10 @@ MCOMMENT=\/\*([^*]|\*[^\/])*\*\/
146158
{ID} { return ID; }
147159
{INT} { return INT; }
148160
{FLOAT} { return FLOAT; }
149-
{PATH} { return PATH; }
150-
{HPATH} { return HPATH; }
161+
"/" / "${" { pushState(PATH); return PATH_SEGMENT; }
162+
{PATH}
163+
| {PATH_SEG}
164+
| {HPATH_START} { pushState(PATH); return PATH_SEGMENT; }
151165
{SPATH} { return SPATH; }
152166
{URI} { return URI; }
153167

src/test/java/org/nixos/idea/lang/ParsingTest.java

+7-4
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,13 @@ public void testWith() {
145145

146146
@Override
147147
protected void tearDown() throws Exception {
148-
// Ensure that the parser does not generate errors even when the errors have
149-
// accidentally been added to the expected result.
150-
ensureNoErrorElements();
151-
super.tearDown();
148+
try {
149+
// Ensure that the parser does not generate errors even when the errors have
150+
// accidentally been added to the expected result.
151+
ensureNoErrorElements();
152+
} finally {
153+
super.tearDown();
154+
}
152155
}
153156

154157
@Override

src/test/java/org/nixos/idea/lang/highlighter/NixSyntaxHighlighterTest.java

+8-6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.intellij.openapi.editor.colors.TextAttributesKey;
44
import com.intellij.psi.TokenType;
55
import com.intellij.psi.tree.IElementType;
6+
import com.intellij.psi.tree.TokenSet;
67
import org.jetbrains.annotations.NotNull;
78
import org.junit.jupiter.api.Named;
89
import org.junit.jupiter.api.Test;
@@ -15,12 +16,12 @@
1516
import java.util.stream.IntStream;
1617
import java.util.stream.Stream;
1718

18-
import static org.junit.jupiter.api.Assertions.assertAll;
19-
import static org.junit.jupiter.api.Assertions.assertEquals;
20-
import static org.junit.jupiter.api.Assertions.assertNotEquals;
21-
import static org.junit.jupiter.api.Assertions.assertNotNull;
19+
import static java.util.function.Predicate.not;
20+
import static org.junit.jupiter.api.Assertions.*;
2221

2322
final class NixSyntaxHighlighterTest {
23+
private static final TokenSet EMPTY_LENGTH_TOKENS = TokenSet.create(NixTypes.PATH_END);
24+
2425
@Test
2526
void testAttributesKeysForUnknownTokenType() {
2627
TextAttributesKey[] tokenHighlights = new NixSyntaxHighlighter().getTokenHighlights(TokenType.CODE_FRAGMENT);
@@ -38,9 +39,10 @@ void testAttributesKeysForKnownTokenTypes(@NotNull IElementType tokenType) {
3839
() -> assertNotNull(tokenHighlights[index], String.format("tokenHighlights[%d]", index))));
3940
}
4041

41-
static @NotNull Stream<Named<IElementType>> testAttributesKeysForKnownTokenTypes() {
42+
static @NotNull Stream<Named<? extends IElementType>> testAttributesKeysForKnownTokenTypes() {
4243
return Stream.concat(
4344
Stream.of(Named.of("TokenType.BAD_CHARACTER", TokenType.BAD_CHARACTER)),
44-
ReflectionUtils.getPublicStaticFieldValues(NixTypes.class, NixTokenType.class));
45+
ReflectionUtils.getPublicStaticFieldValues(NixTypes.class, NixTokenType.class)
46+
).filter(not(named -> EMPTY_LENGTH_TOKENS.contains(named.getPayload())));
4547
}
4648
}

src/test/testData/ParsingTest/List.lexer.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
WHITE_SPACE (' ')
33
INT ('123')
44
WHITE_SPACE (' ')
5-
PATH ('./foo.nix')
5+
PATH_SEGMENT ('./foo.nix')
6+
PATH_END ('')
67
WHITE_SPACE (' ')
78
STRING_OPEN ('"')
89
STR ('abc')

src/test/testData/ParsingTest/List.txt

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ Nix File(0,36)
55
NixLiteralImpl(LITERAL)(2,5)
66
PsiElement(INT)('123')(2,5)
77
PsiWhiteSpace(' ')(5,6)
8-
NixLiteralImpl(LITERAL)(6,15)
9-
PsiElement(PATH)('./foo.nix')(6,15)
8+
NixPathImpl(PATH)(6,15)
9+
PsiElement(PATH_SEGMENT)('./foo.nix')(6,15)
1010
PsiWhiteSpace(' ')(15,16)
1111
NixStdStringImpl(STD_STRING)(16,21)
1212
PsiElement(STRING_OPEN)('"')(16,17)
@@ -33,4 +33,4 @@ Nix File(0,36)
3333
PsiWhiteSpace(' ')(32,33)
3434
PsiElement(})('}')(33,34)
3535
PsiWhiteSpace(' ')(34,35)
36-
PsiElement(])(']')(35,36)
36+
PsiElement(])(']')(35,36)

src/test/testData/ParsingTest/ListWithFunction.lexer.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
WHITE_SPACE (' ')
33
INT ('123')
44
WHITE_SPACE (' ')
5-
PATH ('./foo.nix')
5+
PATH_SEGMENT ('./foo.nix')
6+
PATH_END ('')
67
WHITE_SPACE (' ')
78
STRING_OPEN ('"')
89
STR ('abc')

src/test/testData/ParsingTest/ListWithFunction.txt

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ Nix File(0,38)
55
NixLiteralImpl(LITERAL)(2,5)
66
PsiElement(INT)('123')(2,5)
77
PsiWhiteSpace(' ')(5,6)
8-
NixLiteralImpl(LITERAL)(6,15)
9-
PsiElement(PATH)('./foo.nix')(6,15)
8+
NixPathImpl(PATH)(6,15)
9+
PsiElement(PATH_SEGMENT)('./foo.nix')(6,15)
1010
PsiWhiteSpace(' ')(15,16)
1111
NixStdStringImpl(STD_STRING)(16,21)
1212
PsiElement(STRING_OPEN)('"')(16,17)
@@ -37,4 +37,4 @@ Nix File(0,38)
3737
PsiElement(})('}')(34,35)
3838
PsiElement())(')')(35,36)
3939
PsiWhiteSpace(' ')(36,37)
40-
PsiElement(])(']')(37,38)
40+
PsiElement(])(']')(37,38)
+116-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,123 @@
11
[ ('[')
22
WHITE_SPACE ('\n')
3-
PATH ('/bin/sh')
3+
PATH_SEGMENT ('/bin/sh')
4+
PATH_END ('')
45
WHITE_SPACE ('\n')
5-
PATH ('./builder.sh')
6+
PATH_SEGMENT ('./builder.sh')
7+
PATH_END ('')
68
WHITE_SPACE ('\n')
7-
HPATH ('~/foo')
9+
PATH_SEGMENT ('~/')
10+
PATH_SEGMENT ('foo')
11+
PATH_END ('')
812
WHITE_SPACE ('\n')
913
SPATH ('<nixpkgs>')
1014
WHITE_SPACE ('\n')
11-
] (']')
15+
SCOMMENT ('# antiquotations')
16+
WHITE_SPACE ('\n')
17+
PATH_SEGMENT ('/')
18+
$ ('$')
19+
{ ('{')
20+
ID ('fileName')
21+
} ('}')
22+
PATH_END ('')
23+
WHITE_SPACE ('\n')
24+
PATH_SEGMENT ('/')
25+
$ ('$')
26+
{ ('{')
27+
ID ('fileName')
28+
} ('}')
29+
PATH_SEGMENT ('/')
30+
PATH_END ('')
31+
WHITE_SPACE ('\n')
32+
PATH_SEGMENT ('./')
33+
$ ('$')
34+
{ ('{')
35+
ID ('foo')
36+
} ('}')
37+
PATH_SEGMENT ('-')
38+
$ ('$')
39+
{ ('{')
40+
ID ('bar')
41+
} ('}')
42+
PATH_SEGMENT ('.nix')
43+
PATH_END ('')
44+
WHITE_SPACE ('\n')
45+
PATH_SEGMENT ('./')
46+
$ ('$')
47+
{ ('{')
48+
ID ('foo')
49+
} ('}')
50+
PATH_SEGMENT ('-')
51+
$ ('$')
52+
{ ('{')
53+
ID ('bar')
54+
} ('}')
55+
PATH_SEGMENT ('/')
56+
PATH_END ('')
57+
WHITE_SPACE ('\n')
58+
STRING_OPEN ('"')
59+
$ ('$')
60+
{ ('{')
61+
PATH_SEGMENT ('./foo.txt')
62+
PATH_END ('')
63+
} ('}')
64+
STRING_CLOSE ('"')
65+
WHITE_SPACE ('\n\n')
66+
SCOMMENT ('# whitespace must not be part of paths')
67+
WHITE_SPACE ('\n')
68+
PATH_SEGMENT ('prefix/dir/file.txt')
69+
PATH_END ('')
70+
WHITE_SPACE (' ')
71+
PATH_SEGMENT ('next/path/element')
72+
PATH_END ('')
73+
WHITE_SPACE ('\n\n')
74+
SCOMMENT ('# At least one slash (/) must appear before any interpolated expression for the result to be recognized as a path.')
75+
WHITE_SPACE ('\n')
76+
PATH_SEGMENT ('a/b')
77+
PATH_END ('')
78+
WHITE_SPACE ('\n\n')
79+
SCOMMENT ('# https://nixos.org/manual/nix/stable/language/values.html#type-path')
80+
WHITE_SPACE ('\n')
81+
SCOMMENT ('# a.${foo}/b.${bar} is a syntactically valid division operation.')
82+
WHITE_SPACE ('\n')
83+
SCOMMENT ('# but the Nix parser seems to handle this differently:')
84+
WHITE_SPACE ('\n')
85+
SCOMMENT ('# https://github.com/NixOS/nix-idea/issues/59#issuecomment-1494786812')
86+
WHITE_SPACE ('\n')
87+
ID ('a')
88+
. ('.')
89+
$ ('$')
90+
{ ('{')
91+
ID ('foo')
92+
} ('}')
93+
PATH_SEGMENT ('/b.')
94+
$ ('$')
95+
{ ('{')
96+
ID ('bar')
97+
} ('}')
98+
PATH_END ('')
99+
WHITE_SPACE ('\n\n')
100+
SCOMMENT ('# ./a.${foo}/b.${bar} is a path.')
101+
WHITE_SPACE ('\n')
102+
PATH_SEGMENT ('./a.')
103+
$ ('$')
104+
{ ('{')
105+
ID ('foo')
106+
} ('}')
107+
PATH_SEGMENT ('/')
108+
PATH_SEGMENT ('b.')
109+
$ ('$')
110+
{ ('{')
111+
ID ('bar')
112+
} ('}')
113+
PATH_END ('')
114+
WHITE_SPACE ('\n\n')
115+
SCOMMENT ('# trailing slashes')
116+
WHITE_SPACE ('\n')
117+
PATH_SEGMENT ('/dir/subdir/')
118+
PATH_END ('')
119+
WHITE_SPACE ('\n')
120+
PATH_SEGMENT ('./dir/subdir/')
121+
PATH_END ('')
122+
WHITE_SPACE ('\n')
123+
] (']')

src/test/testData/ParsingTest/Path.nix

+25
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,29 @@
33
./builder.sh
44
~/foo
55
<nixpkgs>
6+
# antiquotations
7+
/${fileName}
8+
/${fileName}/
9+
./${foo}-${bar}.nix
10+
./${foo}-${bar}/
11+
"${./foo.txt}"
12+
13+
# whitespace must not be part of paths
14+
prefix/dir/file.txt next/path/element
15+
16+
# At least one slash (/) must appear before any interpolated expression for the result to be recognized as a path.
17+
a/b
18+
19+
# https://nixos.org/manual/nix/stable/language/values.html#type-path
20+
# a.${foo}/b.${bar} is a syntactically valid division operation.
21+
# but the Nix parser seems to handle this differently:
22+
# https://github.com/NixOS/nix-idea/issues/59#issuecomment-1494786812
23+
a.${foo}/b.${bar}
24+
25+
# ./a.${foo}/b.${bar} is a path.
26+
./a.${foo}/b.${bar}
27+
28+
# trailing slashes
29+
/dir/subdir/
30+
./dir/subdir/
631
]

0 commit comments

Comments
 (0)