diff --git a/grammars/csharp.tmLanguage b/grammars/csharp.tmLanguage
index 50542e5..e10f8a0 100644
--- a/grammars/csharp.tmLanguage
+++ b/grammars/csharp.tmLanguage
@@ -1526,6 +1526,10 @@
(?=\{|where|;)
patterns
+
+ include
+ #base-class-constructor-call
+
include
#type
@@ -1540,6 +1544,62 @@
+ base-class-constructor-call
+
+ begin
+ (?x)
+(?:
+ (@?[_[:alpha:]][_[:alnum:]]*)\s*(\.) # qualified name part
+)*
+(@?[_[:alpha:]][_[:alnum:]]*)\s* # type name
+(
+ <
+ (?<type_args>
+ [^<>()]|
+ \((?:[^<>()]|<[^<>()]*>|\([^<>()]*\))*\)|
+ <\g<type_args>*>
+ )*
+ >\s*
+)? # optional type arguments
+(?=\() # followed by argument list
+ beginCaptures
+
+ 1
+
+ name
+ entity.name.type.cs
+
+ 2
+
+ name
+ punctuation.accessor.cs
+
+ 3
+
+ name
+ entity.name.type.cs
+
+ 4
+
+ patterns
+
+
+ include
+ #type-arguments
+
+
+
+
+ end
+ (?<=\))
+ patterns
+
+
+ include
+ #argument-list
+
+
+
generic-constraints
begin
diff --git a/grammars/csharp.tmLanguage.cson b/grammars/csharp.tmLanguage.cson
index 8886059..206ef97 100644
--- a/grammars/csharp.tmLanguage.cson
+++ b/grammars/csharp.tmLanguage.cson
@@ -959,6 +959,9 @@ repository:
name: "punctuation.separator.colon.cs"
end: "(?=\\{|where|;)"
patterns: [
+ {
+ include: "#base-class-constructor-call"
+ }
{
include: "#type"
}
@@ -969,6 +972,43 @@ repository:
include: "#preprocessor"
}
]
+ "base-class-constructor-call":
+ begin: '''
+ (?x)
+ (?:
+ (@?[_[:alpha:]][_[:alnum:]]*)\\s*(\\.) # qualified name part
+ )*
+ (@?[_[:alpha:]][_[:alnum:]]*)\\s* # type name
+ (
+ <
+ (?
+ [^<>()]|
+ \\((?:[^<>()]|<[^<>()]*>|\\([^<>()]*\\))*\\)|
+ <\\g*>
+ )*
+ >\\s*
+ )? # optional type arguments
+ (?=\\() # followed by argument list
+ '''
+ beginCaptures:
+ "1":
+ name: "entity.name.type.cs"
+ "2":
+ name: "punctuation.accessor.cs"
+ "3":
+ name: "entity.name.type.cs"
+ "4":
+ patterns: [
+ {
+ include: "#type-arguments"
+ }
+ ]
+ end: "(?<=\\))"
+ patterns: [
+ {
+ include: "#argument-list"
+ }
+ ]
"generic-constraints":
begin: "(where)\\s+(@?[_[:alpha:]][_[:alnum:]]*)\\s*(:)"
beginCaptures:
diff --git a/src/csharp.tmLanguage.yml b/src/csharp.tmLanguage.yml
index a2e0d4e..b04c09e 100644
--- a/src/csharp.tmLanguage.yml
+++ b/src/csharp.tmLanguage.yml
@@ -483,10 +483,39 @@ repository:
'0': { name: punctuation.separator.colon.cs }
end: (?=\{|where|;)
patterns:
+ - include: '#base-class-constructor-call'
- include: '#type'
- include: '#punctuation-comma'
- include: '#preprocessor'
+ base-class-constructor-call:
+ begin: |-
+ (?x)
+ (?:
+ (@?[_[:alpha:]][_[:alnum:]]*)\s*(\.) # qualified name part
+ )*
+ (@?[_[:alpha:]][_[:alnum:]]*)\s* # type name
+ (
+ <
+ (?
+ [^<>()]|
+ \((?:[^<>()]|<[^<>()]*>|\([^<>()]*\))*\)|
+ <\g*>
+ )*
+ >\s*
+ )? # optional type arguments
+ (?=\() # followed by argument list
+ beginCaptures:
+ '1': { name: entity.name.type.cs }
+ '2': { name: punctuation.accessor.cs }
+ '3': { name: entity.name.type.cs }
+ '4':
+ patterns:
+ - include: '#type-arguments'
+ end: (?<=\))
+ patterns:
+ - include: '#argument-list'
+
generic-constraints:
begin: (where)\s+(@?[_[:alpha:]][_[:alnum:]]*)\s*(:)
beginCaptures:
diff --git a/test/constructor.tests.ts b/test/constructor.tests.ts
index a435940..2f1405d 100644
--- a/test/constructor.tests.ts
+++ b/test/constructor.tests.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { should } from 'chai';
-import { tokenize, Input, Token } from './utils/tokenize';
+import { tokenize, Input, Token, NamespaceStyle } from './utils/tokenize';
describe("Constructors", () => {
before(() => { should(); });
@@ -415,4 +415,157 @@ public AccountController(
]);
});
});
+
+ describe("Primary Constructors with Base Class Arguments", () => {
+ for (const namespaceStyle of [NamespaceStyle.BlockScoped, NamespaceStyle.FileScoped]) {
+ const styleName = namespaceStyle == NamespaceStyle.BlockScoped
+ ? "Block-Scoped"
+ : "File-Scoped";
+
+ it(`class: primary constructor with base class simple argument (${styleName} Namespace)`, async () => {
+
+ const input = Input.InNamespace(`class Derived(string name) : Base(name) { }`, namespaceStyle);
+ const tokens = await tokenize(input);
+
+ tokens.should.deep.equal([
+ Token.Keyword.Definition.Class,
+ Token.Identifier.ClassName("Derived"),
+ Token.Punctuation.OpenParen,
+ Token.PrimitiveType.String,
+ Token.Identifier.ParameterName("name"),
+ Token.Punctuation.CloseParen,
+ Token.Punctuation.Colon,
+ Token.Type("Base"),
+ Token.Punctuation.OpenParen,
+ Token.Variable.ReadWrite("name"),
+ Token.Punctuation.CloseParen,
+ Token.Punctuation.OpenBrace,
+ Token.Punctuation.CloseBrace
+ ]);
+ });
+
+ it(`class: primary constructor with base class lambda argument (${styleName} Namespace)`, async () => {
+
+ const input = Input.InNamespace(`class Bar(Action action) : Base(() => {}) { }`, namespaceStyle);
+ const tokens = await tokenize(input);
+
+ tokens.should.deep.equal([
+ Token.Keyword.Definition.Class,
+ Token.Identifier.ClassName("Bar"),
+ Token.Punctuation.OpenParen,
+ Token.Type("Action"),
+ Token.Identifier.ParameterName("action"),
+ Token.Punctuation.CloseParen,
+ Token.Punctuation.Colon,
+ Token.Type("Base"),
+ Token.Punctuation.OpenParen,
+ Token.Punctuation.OpenParen,
+ Token.Punctuation.CloseParen,
+ Token.Operator.Arrow,
+ Token.Punctuation.OpenBrace,
+ Token.Punctuation.CloseBrace,
+ Token.Punctuation.CloseParen,
+ Token.Punctuation.OpenBrace,
+ Token.Punctuation.CloseBrace
+ ]);
+ });
+
+ it(`class: primary constructor with base class multiple arguments (${styleName} Namespace)`, async () => {
+
+ const input = Input.InNamespace(`class Child(int x, string y) : Parent(x, y) { }`, namespaceStyle);
+ const tokens = await tokenize(input);
+
+ tokens.should.deep.equal([
+ Token.Keyword.Definition.Class,
+ Token.Identifier.ClassName("Child"),
+ Token.Punctuation.OpenParen,
+ Token.PrimitiveType.Int,
+ Token.Identifier.ParameterName("x"),
+ Token.Punctuation.Comma,
+ Token.PrimitiveType.String,
+ Token.Identifier.ParameterName("y"),
+ Token.Punctuation.CloseParen,
+ Token.Punctuation.Colon,
+ Token.Type("Parent"),
+ Token.Punctuation.OpenParen,
+ Token.Variable.ReadWrite("x"),
+ Token.Punctuation.Comma,
+ Token.Variable.ReadWrite("y"),
+ Token.Punctuation.CloseParen,
+ Token.Punctuation.OpenBrace,
+ Token.Punctuation.CloseBrace
+ ]);
+ });
+
+ it(`class: primary constructor with base class and interface (${styleName} Namespace)`, async () => {
+
+ const input = Input.InNamespace(`class Derived(string name) : Base(name), IInterface { }`, namespaceStyle);
+ const tokens = await tokenize(input);
+
+ tokens.should.deep.equal([
+ Token.Keyword.Definition.Class,
+ Token.Identifier.ClassName("Derived"),
+ Token.Punctuation.OpenParen,
+ Token.PrimitiveType.String,
+ Token.Identifier.ParameterName("name"),
+ Token.Punctuation.CloseParen,
+ Token.Punctuation.Colon,
+ Token.Type("Base"),
+ Token.Punctuation.OpenParen,
+ Token.Variable.ReadWrite("name"),
+ Token.Punctuation.CloseParen,
+ Token.Punctuation.Comma,
+ Token.Type("IInterface"),
+ Token.Punctuation.OpenBrace,
+ Token.Punctuation.CloseBrace
+ ]);
+ });
+
+ it(`record: primary constructor with base class simple argument (${styleName} Namespace)`, async () => {
+
+ const input = Input.InNamespace(`record Derived(string name) : Base(name);`, namespaceStyle);
+ const tokens = await tokenize(input);
+
+ tokens.should.deep.equal([
+ Token.Keyword.Definition.Record,
+ Token.Identifier.ClassName("Derived"),
+ Token.Punctuation.OpenParen,
+ Token.PrimitiveType.String,
+ Token.Identifier.ParameterName("name"),
+ Token.Punctuation.CloseParen,
+ Token.Punctuation.Colon,
+ Token.Type("Base"),
+ Token.Punctuation.OpenParen,
+ Token.Variable.ReadWrite("name"),
+ Token.Punctuation.CloseParen,
+ Token.Punctuation.Semicolon
+ ]);
+ });
+
+ it(`record: primary constructor with base class lambda argument (${styleName} Namespace)`, async () => {
+
+ const input = Input.InNamespace(`record Bar(Action action) : Base(() => {});`, namespaceStyle);
+ const tokens = await tokenize(input);
+
+ tokens.should.deep.equal([
+ Token.Keyword.Definition.Record,
+ Token.Identifier.ClassName("Bar"),
+ Token.Punctuation.OpenParen,
+ Token.Type("Action"),
+ Token.Identifier.ParameterName("action"),
+ Token.Punctuation.CloseParen,
+ Token.Punctuation.Colon,
+ Token.Type("Base"),
+ Token.Punctuation.OpenParen,
+ Token.Punctuation.OpenParen,
+ Token.Punctuation.CloseParen,
+ Token.Operator.Arrow,
+ Token.Punctuation.OpenBrace,
+ Token.Punctuation.CloseBrace,
+ Token.Punctuation.CloseParen,
+ Token.Punctuation.Semicolon
+ ]);
+ });
+ }
+ });
});
\ No newline at end of file