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