Skip to content

Commit 82142c6

Browse files
Merge pull request #1014 from ivanz/ivan-class-syntax-improvements
Syntax highlighting improvements and tests
2 parents 425dd6b + cb1394a commit 82142c6

File tree

5 files changed

+345
-16
lines changed

5 files changed

+345
-16
lines changed

.editorconfig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ trim_trailing_whitespace = true
1313
[{.travis.yml},package.json]
1414
indent_style = space
1515
indent_size = 2
16+
17+
[syntaxes/csharp.json]
18+
indent_style = space
19+
indent_size = 2

syntaxes/csharp.json

Lines changed: 127 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,47 @@
7171
}
7272
]
7373
},
74+
"field-declaration": {
75+
"patterns": [
76+
{
77+
"begin": "(?=(?:(?:(?:private|public|volatile|internal|protected|static|readonly|const)\\s*)*)(?:[\\w\\s,<>\\[\\]]+?)(?:[\\w]+)\\s*(?:;|=|=>))",
78+
"end": "(?=;)",
79+
"patterns": [
80+
{
81+
"match": "^\\s*((?:(?:private|public|volatile|internal|protected|static|readonly|const)\\s*)*)\\s*(.+?)\\s*([\\w]+)\\s*(?=;|=)",
82+
"captures": {
83+
"1" : {
84+
"patterns": [
85+
{
86+
"include": "#storage-modifiers"
87+
}
88+
]
89+
},
90+
"2" : {
91+
"patterns": [
92+
{
93+
"include": "#type"
94+
}
95+
]
96+
},
97+
"3": {
98+
"name": "entity.name.variable.cs"
99+
}
100+
}
101+
},
102+
{
103+
"begin": "(?==>?)",
104+
"end": "(?=;)",
105+
"patterns": [
106+
{
107+
"include": "#code"
108+
}
109+
]
110+
}
111+
]
112+
}
113+
]
114+
},
74115
"variable": {
75116
"patterns": [
76117
{
@@ -123,6 +164,60 @@
123164
}
124165
]
125166
},
167+
"type": {
168+
"patterns": [
169+
{
170+
"match": "(\\w+\\s*<(?:[\\w\\s,\\.`\\[\\]\\*]+|\\g<1>)+>(?:\\s*\\[\\s*\\])?)",
171+
"comment": "generic type",
172+
"captures": {
173+
"1": {
174+
"name": "storage.type.cs"
175+
}
176+
}
177+
},
178+
{
179+
"match": "\\b([a-zA-Z]+[\\w]*\\b(?:\\s*\\[\\s*\\])?\\*?)",
180+
"comment": "non-generic type",
181+
"captures": {
182+
"1": {
183+
"name": "storage.type.cs"
184+
}
185+
}
186+
}
187+
]
188+
},
189+
"generic-constraints": {
190+
"begin": "(where)\\s+(\\w+)\\s*:",
191+
"end": "(?=where|{|$)",
192+
"beginCaptures": {
193+
"1": {
194+
"name": "keyword.other.cs"
195+
},
196+
"2": {
197+
"name": "storage.type.cs"
198+
}
199+
},
200+
"patterns": [
201+
{
202+
"match": "\\b(class|struct)\\b",
203+
"name": "keyword.other.cs"
204+
},
205+
{
206+
"match": "(new)\\s*\\(\\s*\\)",
207+
"captures": {
208+
"1": {
209+
"name": "keyword.other.cs"
210+
}
211+
}
212+
},
213+
{
214+
"include": "#type"
215+
},
216+
{
217+
"include": "#generic-constraints"
218+
}
219+
]
220+
},
126221
"class": {
127222
"begin": "(?=\\w?[\\w\\s]*[^@]?(?:class|struct|interface|enum)\\s+\\w+)",
128223
"end": "}",
@@ -140,31 +235,40 @@
140235
"include": "#comments"
141236
},
142237
{
143-
"captures": {
238+
"begin": "(class|struct|interface|enum)\\s+",
239+
"end": "(?={|:|$|where)",
240+
"name": "meta.class.identifier.cs",
241+
"beginCaptures": {
144242
"1": {
145243
"name": "storage.modifier.cs"
146-
},
147-
"2": {
148-
"name": "entity.name.type.class.cs"
149244
}
150245
},
151-
"match": "(class|struct|interface|enum)\\s+(\\w+)",
152-
"name": "meta.class.identifier.cs"
246+
"patterns": [
247+
{
248+
"include": "#type"
249+
}
250+
]
153251
},
154252
{
155253
"begin": ":",
156-
"end": "(?={)",
254+
"end": "(?={|where)",
157255
"patterns": [
158256
{
257+
"include": "#type"
258+
},
259+
{
260+
"match": "([\\w<>]+)\\s*",
159261
"captures": {
160262
"1": {
161263
"name": "storage.type.cs"
162264
}
163-
},
164-
"match": "\\s*,?([A-Za-z_]\\w*)\\b"
265+
}
165266
}
166267
]
167268
},
269+
{
270+
"include": "#generic-constraints"
271+
},
168272
{
169273
"begin": "{",
170274
"beginCaptures": {
@@ -176,15 +280,25 @@
176280
"name": "meta.class.body.cs",
177281
"patterns": [
178282
{
179-
"include": "#method"
180-
},
181-
{
182-
"include": "#code"
283+
"include": "#type-body"
183284
}
184285
]
185286
}
186287
]
187288
},
289+
"type-body": {
290+
"patterns": [
291+
{
292+
"include": "#field-declaration"
293+
},
294+
{
295+
"include": "#method"
296+
},
297+
{
298+
"include": "#code"
299+
}
300+
]
301+
},
188302
"code": {
189303
"patterns": [
190304
{

test/syntaxes/class.tests.ts

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ describe("Grammar", function() {
88
});
99

1010
describe("Class", function() {
11-
it("has a class keyword, a name and optional storage modifiers", function() {
11+
it("class keyword and storage modifiers", function() {
1212

1313
const input = `
1414
namespace TestNamespace
@@ -73,6 +73,75 @@ namespace TestNamespace
7373

7474
});
7575

76+
it("generics in identifier", function () {
77+
78+
const input = `
79+
namespace TestNamespace
80+
{
81+
class Dictionary<T, Dictionary<string, string>> { }
82+
}`;
83+
let tokens: Token[] = TokenizerUtil.tokenize(input);
84+
85+
tokens.should.contain(Tokens.ClassKeyword("class", 4, 5));
86+
tokens.should.contain(Tokens.ClassIdentifier("Dictionary<T, Dictionary<string, string>>", 4, 11));
87+
});
88+
89+
it("inheritance", function() {
90+
91+
const input = `
92+
namespace TestNamespace
93+
{
94+
class PublicClass : IInterface, IInterfaceTwo { }
95+
class PublicClass<T> : IInterface<T>, IInterfaceTwo { }
96+
class PublicClass<T> : Dictionary<T, Dictionary<string, string>>, IMap<T, Dictionary<string, string>> { }
97+
}`;
98+
let tokens: Token[] = TokenizerUtil.tokenize(input);
99+
100+
tokens.should.contain(Tokens.ClassKeyword("class", 4, 5));
101+
tokens.should.contain(Tokens.ClassIdentifier("PublicClass", 4, 11));
102+
tokens.should.contain(Tokens.Type("IInterface", 4, 28));
103+
tokens.should.contain(Tokens.Type("IInterfaceTwo", 4, 43));
104+
105+
tokens.should.contain(Tokens.ClassKeyword("class", 5, 5));
106+
tokens.should.contain(Tokens.ClassIdentifier("PublicClass<T>", 5, 11));
107+
tokens.should.contain(Tokens.Type("IInterface<T>", 5, 28));
108+
tokens.should.contain(Tokens.Type("IInterfaceTwo", 5, 43));
109+
110+
tokens.should.contain(Tokens.Type("Dictionary<T, Dictionary<string, string>>", 6, 28));
111+
tokens.should.contain(Tokens.Type("IMap<T, Dictionary<string, string>>", 6, 71));
112+
});
113+
114+
it("generic constraints", function() {
115+
116+
const input = `
117+
namespace TestNamespace
118+
{
119+
class PublicClass<T> where T : ISomething { }
120+
class PublicClass<T, X> : Dictionary<T, List<string>[]>, ISomething where T : ICar, new() where X : struct { }
121+
}`;
122+
let tokens: Token[] = TokenizerUtil.tokenize(input);
123+
124+
tokens.should.contain(Tokens.ClassKeyword("class", 4, 5));
125+
tokens.should.contain(Tokens.ClassIdentifier("PublicClass<T>", 4, 11));
126+
tokens.should.contain(Tokens.Keyword("where", 4, 26));
127+
tokens.should.contain(Tokens.Type("T", 4, 32));
128+
tokens.should.contain(Tokens.Type("ISomething", 4, 36));
129+
130+
tokens.should.contain(Tokens.ClassKeyword("class", 5, 5));
131+
tokens.should.contain(Tokens.ClassIdentifier("PublicClass<T, X>", 5, 11));
132+
tokens.should.contain(Tokens.Type("Dictionary<T, List<string>[]>", 5, 31));
133+
tokens.should.contain(Tokens.Type("ISomething", 5, 62));
134+
tokens.should.contain(Tokens.Keyword("where", 5, 73));
135+
tokens.should.contain(Tokens.Type("T", 5, 79));
136+
tokens.should.contain(Tokens.Type("ICar", 5, 83));
137+
tokens.should.contain(Tokens.Keyword("new", 5, 89));
138+
tokens.should.contain(Tokens.Keyword("where", 5, 95));
139+
tokens.should.contain(Tokens.Type("X", 5, 101));
140+
tokens.should.contain(Tokens.Keyword("struct", 5, 105));
141+
142+
});
143+
144+
76145
});
77146
});
78147

0 commit comments

Comments
 (0)