accounts;`);
130 | const tokens = await tokenize(input);
131 |
132 | tokens.should.deep.equal([
133 | Token.Type('Map'),
134 | Token.Punctuation.TypeParameters.Begin,
135 | { text: 'ID', type: 'keyword.type.apex' },
136 | Token.Punctuation.Comma,
137 | Token.Type('Account'),
138 | Token.Punctuation.TypeParameters.End,
139 | Token.Identifiers.FieldName('accounts'),
140 | Token.Punctuation.Semicolon,
141 | ]);
142 | });
143 | });
144 | });
145 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Salesforce Open Source Community Code of Conduct
2 |
3 | ## About the Code of Conduct
4 |
5 | Equality is a core value at Salesforce. We believe a diverse and inclusive
6 | community fosters innovation and creativity, and are committed to building a
7 | culture where everyone feels included.
8 |
9 | Salesforce open-source projects are committed to providing a friendly, safe, and
10 | welcoming environment for all, regardless of gender identity and expression,
11 | sexual orientation, disability, physical appearance, body size, ethnicity, nationality,
12 | race, age, religion, level of experience, education, socioeconomic status, or
13 | other similar personal characteristics.
14 |
15 | The goal of this code of conduct is to specify a baseline standard of behavior so
16 | that people with different social values and communication styles can work
17 | together effectively, productively, and respectfully in our open source community.
18 | It also establishes a mechanism for reporting issues and resolving conflicts.
19 |
20 | All questions and reports of abusive, harassing, or otherwise unacceptable behavior
21 | in a Salesforce open-source project may be reported by contacting the Salesforce
22 | Open Source Conduct Committee at ossconduct@salesforce.com.
23 |
24 | ## Our Pledge
25 |
26 | In the interest of fostering an open and welcoming environment, we as
27 | contributors and maintainers pledge to making participation in our project and
28 | our community a harassment-free experience for everyone, regardless of gender
29 | identity and expression, sexual orientation, disability, physical appearance,
30 | body size, ethnicity, nationality, race, age, religion, level of experience, education,
31 | socioeconomic status, or other similar personal characteristics.
32 |
33 | ## Our Standards
34 |
35 | Examples of behavior that contributes to creating a positive environment
36 | include:
37 |
38 | - Using welcoming and inclusive language
39 | - Being respectful of differing viewpoints and experiences
40 | - Gracefully accepting constructive criticism
41 | - Focusing on what is best for the community
42 | - Showing empathy toward other community members
43 |
44 | Examples of unacceptable behavior by participants include:
45 |
46 | - The use of sexualized language or imagery and unwelcome sexual attention or
47 | advances
48 | - Personal attacks, insulting/derogatory comments, or trolling
49 | - Public or private harassment
50 | - Publishing, or threatening to publish, others' private information—such as
51 | a physical or electronic address—without explicit permission
52 | - Other conduct which could reasonably be considered inappropriate in a
53 | professional setting
54 | - Advocating for or encouraging any of the above behaviors
55 |
56 | ## Our Responsibilities
57 |
58 | Project maintainers are responsible for clarifying the standards of acceptable
59 | behavior and are expected to take appropriate and fair corrective action in
60 | response to any instances of unacceptable behavior.
61 |
62 | Project maintainers have the right and responsibility to remove, edit, or
63 | reject comments, commits, code, wiki edits, issues, and other contributions
64 | that are not aligned with this Code of Conduct, or to ban temporarily or
65 | permanently any contributor for other behaviors that they deem inappropriate,
66 | threatening, offensive, or harmful.
67 |
68 | ## Scope
69 |
70 | This Code of Conduct applies both within project spaces and in public spaces
71 | when an individual is representing the project or its community. Examples of
72 | representing a project or community include using an official project email
73 | address, posting via an official social media account, or acting as an appointed
74 | representative at an online or offline event. Representation of a project may be
75 | further defined and clarified by project maintainers.
76 |
77 | ## Enforcement
78 |
79 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
80 | reported by contacting the Salesforce Open Source Conduct Committee
81 | at ossconduct@salesforce.com. All complaints will be reviewed and investigated
82 | and will result in a response that is deemed necessary and appropriate to the
83 | circumstances. The committee is obligated to maintain confidentiality with
84 | regard to the reporter of an incident. Further details of specific enforcement
85 | policies may be posted separately.
86 |
87 | Project maintainers who do not follow or enforce the Code of Conduct in good
88 | faith may face temporary or permanent repercussions as determined by other
89 | members of the project's leadership and the Salesforce Open Source Conduct
90 | Committee.
91 |
92 | ## Attribution
93 |
94 | This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant-home],
95 | version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html.
96 | It includes adaptions and additions from [Go Community Code of Conduct][golang-coc],
97 | [CNCF Code of Conduct][cncf-coc], and [Microsoft Open Source Code of Conduct][microsoft-coc].
98 |
99 | This Code of Conduct is licensed under the [Creative Commons Attribution 3.0 License][cc-by-3-us].
100 |
101 | [contributor-covenant-home]: https://www.contributor-covenant.org 'https://www.contributor-covenant.org/'
102 | [golang-coc]: https://golang.org/conduct
103 | [cncf-coc]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md
104 | [microsoft-coc]: https://opensource.microsoft.com/codeofconduct/
105 | [cc-by-3-us]: https://creativecommons.org/licenses/by/3.0/us/
106 |
--------------------------------------------------------------------------------
/test/javadoc.test.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) 2020 Salesforce. All rights reserved.
3 | * See LICENSE in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { should } from 'chai';
7 | import { tokenize, Input, Token } from './utils/tokenize';
8 |
9 | describe('Grammar', () => {
10 | before(() => {
11 | should();
12 | });
13 | describe('JavaDoc', () => {
14 | it('multi-line comment with no content', async () => {
15 | const input = `/***********/`;
16 | const tokens = await tokenize(input);
17 |
18 | tokens.should.deep.equal([
19 | Token.Comment.MultiLine.JavaDocStart,
20 | Token.Comment.MultiLine.JavaDocText('********'),
21 | Token.Comment.MultiLine.End,
22 | ]);
23 | });
24 |
25 | it('multi-line java doc comment', async () => {
26 | const input = Input.FromText(`
27 | /**
28 | * foo
29 | */
30 | `);
31 | const tokens = await tokenize(input);
32 |
33 | tokens.should.deep.equal([
34 | Token.Comment.MultiLine.JavaDocStart,
35 | Token.Comment.MultiLine.JavaDocText('* foo'),
36 | Token.Comment.MultiLine.End,
37 | ]);
38 | });
39 |
40 | it('multi-line comment with content', async () => {
41 | const input = Input.FromText(`
42 | /*************/
43 | /***** foo ***/
44 | /*************/
45 | `);
46 | const tokens = await tokenize(input);
47 |
48 | tokens.should.deep.equal([
49 | Token.Comment.MultiLine.JavaDocStart,
50 | Token.Comment.MultiLine.JavaDocText('**********'),
51 | Token.Comment.MultiLine.End,
52 | Token.Comment.MultiLine.JavaDocStart,
53 | Token.Comment.MultiLine.JavaDocText('*** foo **'),
54 | Token.Comment.MultiLine.End,
55 | Token.Comment.MultiLine.JavaDocStart,
56 | Token.Comment.MultiLine.JavaDocText('**********'),
57 | Token.Comment.MultiLine.End,
58 | ]);
59 | });
60 |
61 | it('multi-line comment with content', async () => {
62 | const input = Input.FromText(`
63 | /**
64 | * @return null
65 | */
66 | `);
67 | const tokens = await tokenize(input);
68 |
69 | tokens.should.deep.equal([
70 | Token.Comment.MultiLine.JavaDocStart,
71 | Token.Comment.MultiLine.JavaDocText('* '),
72 | Token.Comment.MultiLine.JavaDocKeyword('@return'),
73 | Token.Comment.MultiLine.JavaDocText(' null'),
74 | Token.Comment.MultiLine.End,
75 | ]);
76 | });
77 |
78 | it('multi-line comment with annotations', async () => {
79 | const input = Input.FromText(`
80 | /**
81 | * Interactively reinvent high-payoff convergence
82 | * with professional process improvements.
83 | *
84 | * Continually initiate client-centric mindshare
85 | * without innovative value. Compellingly formulate
86 | * sustainable e-business via revolutionary supply chains.
87 | *
88 | * @param url base location url
89 | * @param name the name of the asset
90 | * @return the full URL of the asset
91 | *
92 | * @throws myCustomException
93 | * @author Eduardo Mora em@example.com
94 | */`);
95 | const tokens = await tokenize(input);
96 |
97 | tokens.should.deep.equal([
98 | Token.Comment.MultiLine.JavaDocStart,
99 | Token.Comment.MultiLine.JavaDocText(
100 | ' * Interactively reinvent high-payoff convergence'
101 | ),
102 | Token.Comment.MultiLine.JavaDocText(
103 | ' * with professional process improvements.'
104 | ),
105 | Token.Comment.MultiLine.JavaDocText(' *
'),
106 | Token.Comment.MultiLine.JavaDocText(
107 | ' * Continually initiate client-centric mindshare'
108 | ),
109 | Token.Comment.MultiLine.JavaDocText(
110 | ' * without innovative value. Compellingly formulate '
111 | ),
112 | Token.Comment.MultiLine.JavaDocText(
113 | ' * sustainable e-business via revolutionary supply chains.'
114 | ),
115 | Token.Comment.MultiLine.JavaDocText(' *'),
116 | Token.Comment.MultiLine.JavaDocText(' * '),
117 | Token.Comment.MultiLine.JavaDocKeyword('@param'),
118 | Token.Comment.MultiLine.JavaDocText(' '),
119 | Token.Identifiers.ParameterName('url'),
120 | Token.Comment.MultiLine.JavaDocText(' base location url'),
121 | Token.Comment.MultiLine.JavaDocText(' * '),
122 | Token.Comment.MultiLine.JavaDocKeyword('@param'),
123 | Token.Comment.MultiLine.JavaDocText(' '),
124 | Token.Identifiers.ParameterName('name'),
125 | Token.Comment.MultiLine.JavaDocText(' the name of the asset'),
126 | Token.Comment.MultiLine.JavaDocText(' * '),
127 | Token.Comment.MultiLine.JavaDocKeyword('@return'),
128 | Token.Comment.MultiLine.JavaDocText(' the full URL of the asset'),
129 | Token.Comment.MultiLine.JavaDocText(' * '),
130 | Token.Comment.MultiLine.JavaDocText(' * '),
131 | Token.Comment.MultiLine.JavaDocKeyword('@throws'),
132 | Token.Comment.MultiLine.JavaDocText(' '),
133 | Token.Identifiers.ClassName('myCustomException'),
134 | Token.Comment.MultiLine.JavaDocText(' * '),
135 | Token.Comment.MultiLine.JavaDocKeyword('@author'),
136 | Token.Comment.MultiLine.JavaDocText(' Eduardo Mora em@example.com'),
137 | Token.Comment.MultiLine.JavaDocText(' '),
138 | Token.Comment.MultiLine.End,
139 | ]);
140 | });
141 | });
142 | });
143 |
--------------------------------------------------------------------------------
/test/field.test.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Modifications Copyright (c) 2018 Salesforce.
4 | * See LICENSE in the project root for license information.
5 | *--------------------------------------------------------------------------------------------*/
6 |
7 | import { should } from 'chai';
8 | import { tokenize, Input, Token } from './utils/tokenize';
9 |
10 | describe('Grammar', () => {
11 | before(() => {
12 | should();
13 | });
14 |
15 | describe('Field', () => {
16 | it('declaration', async () => {
17 | const input = Input.InClass(`
18 | private List _field;
19 | private List field;
20 | private List field123;`);
21 |
22 | const tokens = await tokenize(input);
23 |
24 | tokens.should.deep.equal([
25 | Token.Keywords.Modifiers.Private,
26 | Token.Type('List'),
27 | Token.Identifiers.FieldName('_field'),
28 | Token.Punctuation.Semicolon,
29 |
30 | Token.Keywords.Modifiers.Private,
31 | Token.Type('List'),
32 | Token.Identifiers.FieldName('field'),
33 | Token.Punctuation.Semicolon,
34 |
35 | Token.Keywords.Modifiers.Private,
36 | Token.Type('List'),
37 | Token.Identifiers.FieldName('field123'),
38 | Token.Punctuation.Semicolon,
39 | ]);
40 | });
41 |
42 | it('generic', async () => {
43 | const input = Input.InClass(
44 | `private Dictionary< List, Dictionary> _field;`
45 | );
46 | const tokens = await tokenize(input);
47 |
48 | tokens.should.deep.equal([
49 | Token.Keywords.Modifiers.Private,
50 | Token.Type('Dictionary'),
51 | Token.Punctuation.TypeParameters.Begin,
52 | Token.Type('List'),
53 | Token.Punctuation.TypeParameters.Begin,
54 | Token.Type('T'),
55 | Token.Punctuation.TypeParameters.End,
56 | Token.Punctuation.Comma,
57 | Token.Type('Dictionary'),
58 | Token.Punctuation.TypeParameters.Begin,
59 | Token.Type('T'),
60 | Token.Punctuation.Comma,
61 | Token.Type('D'),
62 | Token.Punctuation.TypeParameters.End,
63 | Token.Punctuation.TypeParameters.End,
64 | Token.Identifiers.FieldName('_field'),
65 | Token.Punctuation.Semicolon,
66 | ]);
67 | });
68 |
69 | it('types', async () => {
70 | const input = Input.InClass(`
71 | String field123;
72 | String[] field123;`);
73 |
74 | const tokens = await tokenize(input);
75 |
76 | tokens.should.deep.equal([
77 | Token.PrimitiveType.String,
78 | Token.Identifiers.FieldName('field123'),
79 | Token.Punctuation.Semicolon,
80 |
81 | Token.PrimitiveType.String,
82 | Token.Punctuation.OpenBracket,
83 | Token.Punctuation.CloseBracket,
84 | Token.Identifiers.FieldName('field123'),
85 | Token.Punctuation.Semicolon,
86 | ]);
87 | });
88 |
89 | it('assignment', async () => {
90 | const input = Input.InClass(`
91 | private String field = 'hello';
92 | Boolean field = true;`);
93 |
94 | let tokens = await tokenize(input);
95 |
96 | tokens.should.deep.equal([
97 | Token.Keywords.Modifiers.Private,
98 | Token.PrimitiveType.String,
99 | Token.Identifiers.FieldName('field'),
100 | Token.Operators.Assignment,
101 | Token.Punctuation.String.Begin,
102 | Token.Literals.String('hello'),
103 | Token.Punctuation.String.End,
104 | Token.Punctuation.Semicolon,
105 |
106 | Token.PrimitiveType.Boolean,
107 | Token.Identifiers.FieldName('field'),
108 | Token.Operators.Assignment,
109 | Token.Literals.Boolean.True,
110 | Token.Punctuation.Semicolon,
111 | ]);
112 | });
113 |
114 | it('declaration with multiple declarators', async () => {
115 | const input = Input.InClass(`Integer x = 19, y = 23, z = 42;`);
116 | const tokens = await tokenize(input);
117 |
118 | tokens.should.deep.equal([
119 | Token.PrimitiveType.Integer,
120 | Token.Identifiers.FieldName('x'),
121 | Token.Operators.Assignment,
122 | Token.Literals.Numeric.Decimal('19'),
123 | Token.Punctuation.Comma,
124 | Token.Identifiers.FieldName('y'),
125 | Token.Operators.Assignment,
126 | Token.Literals.Numeric.Decimal('23'),
127 | Token.Punctuation.Comma,
128 | Token.Identifiers.FieldName('z'),
129 | Token.Operators.Assignment,
130 | Token.Literals.Numeric.Decimal('42'),
131 | Token.Punctuation.Semicolon,
132 | ]);
133 | });
134 |
135 | it('type with no names and no modifiers', async () => {
136 | const input = Input.InClass(`public static Integer x;`);
137 | const tokens = await tokenize(input);
138 |
139 | tokens.should.deep.equal([
140 | Token.Keywords.Modifiers.Public,
141 | Token.Keywords.Modifiers.Static,
142 | Token.PrimitiveType.Integer,
143 | Token.Identifiers.FieldName('x'),
144 | Token.Punctuation.Semicolon,
145 | ]);
146 | });
147 |
148 | it('Fields with fully-qualified names are highlighted properly (issue omnisharp-vscode#1097)', async () => {
149 | const input = Input.InClass(`
150 | private CanvasGroup[] groups;
151 | private UnityEngine.UI.Image[] selectedImages;
152 | `);
153 | const tokens = await tokenize(input);
154 |
155 | tokens.should.deep.equal([
156 | Token.Keywords.Modifiers.Private,
157 | Token.Type('CanvasGroup'),
158 | Token.Punctuation.OpenBracket,
159 | Token.Punctuation.CloseBracket,
160 | Token.Identifiers.FieldName('groups'),
161 | Token.Punctuation.Semicolon,
162 | Token.Keywords.Modifiers.Private,
163 | Token.Type('UnityEngine'),
164 | Token.Punctuation.Accessor,
165 | Token.Type('UI'),
166 | Token.Punctuation.Accessor,
167 | Token.Type('Image'),
168 | Token.Punctuation.OpenBracket,
169 | Token.Punctuation.CloseBracket,
170 | Token.Identifiers.FieldName('selectedImages'),
171 | Token.Punctuation.Semicolon,
172 | ]);
173 | });
174 | });
175 | });
176 |
--------------------------------------------------------------------------------
/test/annotation.test.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) 2018 Salesforce. All rights reserved.
3 | * See LICENSE in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { should } from 'chai';
7 | import { tokenize, Input, Token } from './utils/tokenize';
8 |
9 | describe('Grammar', () => {
10 | before(() => {
11 | should();
12 | });
13 |
14 | describe('Annotation', () => {
15 | it('annotation on methods', async () => {
16 | const input = Input.InClass(`@deprecated
17 | // This method is deprecated. Use myOptimizedMethod(String a, String b) instead.
18 | global void myMethod(String a) {
19 | }`);
20 | const tokens = await tokenize(input);
21 |
22 | tokens.should.deep.equal([
23 | Token.Keywords.Modifiers.AnnotationName('@deprecated'),
24 | Token.Comment.LeadingWhitespace(' '),
25 | Token.Comment.SingleLine.Start,
26 | Token.Comment.SingleLine.Text(
27 | ' This method is deprecated. Use myOptimizedMethod(String a, String b) instead.'
28 | ),
29 | Token.Keywords.Modifiers.Global,
30 | Token.PrimitiveType.Void,
31 | Token.Identifiers.MethodName('myMethod'),
32 | Token.Punctuation.OpenParen,
33 | Token.PrimitiveType.String,
34 | Token.Identifiers.ParameterName('a'),
35 | Token.Punctuation.CloseParen,
36 | Token.Punctuation.OpenBrace,
37 | Token.Punctuation.CloseBrace,
38 | ]);
39 | });
40 |
41 | it('annotation with one parameter', async () => {
42 | const input = Input.InClass(`@future (callout=true)
43 | public static void doCalloutFromFuture() {
44 | }`);
45 | const tokens = await tokenize(input);
46 |
47 | tokens.should.deep.equal([
48 | Token.Keywords.Modifiers.AnnotationName('@future'),
49 | Token.Punctuation.OpenParen,
50 | Token.Variables.ReadWrite('callout'),
51 | Token.Operators.Assignment,
52 | Token.Literals.Boolean.True,
53 | Token.Punctuation.CloseParen,
54 | Token.Keywords.Modifiers.Public,
55 | Token.Keywords.Modifiers.Static,
56 | Token.PrimitiveType.Void,
57 | Token.Identifiers.MethodName('doCalloutFromFuture'),
58 | Token.Punctuation.OpenParen,
59 | Token.Punctuation.CloseParen,
60 | Token.Punctuation.OpenBrace,
61 | Token.Punctuation.CloseBrace,
62 | ]);
63 | });
64 |
65 | it('annotation on class', async () => {
66 | const input = Input.FromText(`@isTest
67 | public class MyTestClass { }`);
68 |
69 | const tokens = await tokenize(input);
70 |
71 | tokens.should.deep.equal([
72 | Token.Keywords.Modifiers.AnnotationName('@isTest'),
73 | Token.Keywords.Modifiers.Public,
74 | Token.Keywords.Class,
75 | Token.Identifiers.ClassName('MyTestClass'),
76 | Token.Punctuation.OpenBrace,
77 | Token.Punctuation.CloseBrace,
78 | ]);
79 | });
80 |
81 | it('annotation with multiple parameters', async () => {
82 | const input = Input.FromText(
83 | `@InvocableMethod(label='Insert Accounts' description='Inserts new accounts.')`
84 | );
85 | const tokens = await tokenize(input);
86 |
87 | tokens.should.deep.equal([
88 | Token.Keywords.Modifiers.AnnotationName('@InvocableMethod'),
89 | Token.Punctuation.OpenParen,
90 | Token.Variables.ReadWrite('label'),
91 | Token.Operators.Assignment,
92 | Token.Punctuation.String.Begin,
93 | Token.XmlDocComments.String.SingleQuoted.Text('Insert Accounts'),
94 | Token.Punctuation.String.End,
95 | Token.Variables.ReadWrite('description'),
96 | Token.Operators.Assignment,
97 | Token.Punctuation.String.Begin,
98 | Token.XmlDocComments.String.SingleQuoted.Text('Inserts new accounts.'),
99 | Token.Punctuation.String.End,
100 | Token.Punctuation.CloseParen,
101 | ]);
102 | });
103 |
104 | it('annotation with multiple parameters on field', async () => {
105 | const input =
106 | Input.InClass(`@InvocableMethod(label='Insert Accounts' description='Inserts new accounts.' required=false)
107 | global Id leadId;
108 | `);
109 | const tokens = await tokenize(input);
110 |
111 | tokens.should.deep.equal([
112 | Token.Keywords.Modifiers.AnnotationName('@InvocableMethod'),
113 | Token.Punctuation.OpenParen,
114 | Token.Variables.ReadWrite('label'),
115 | Token.Operators.Assignment,
116 | Token.Punctuation.String.Begin,
117 | Token.XmlDocComments.String.SingleQuoted.Text('Insert Accounts'),
118 | Token.Punctuation.String.End,
119 | Token.Variables.ReadWrite('description'),
120 | Token.Operators.Assignment,
121 | Token.Punctuation.String.Begin,
122 | Token.XmlDocComments.String.SingleQuoted.Text('Inserts new accounts.'),
123 | Token.Punctuation.String.End,
124 | Token.Variables.ReadWrite('required'),
125 | Token.Operators.Assignment,
126 | Token.Literals.Boolean.False,
127 | Token.Punctuation.CloseParen,
128 | Token.Keywords.Modifiers.Global,
129 | Token.PrimitiveType.ID,
130 | Token.Identifiers.FieldName('leadId'),
131 | Token.Punctuation.Semicolon,
132 | ]);
133 | });
134 |
135 | it('annotation on same line as method declaration (issue #44)', async () => {
136 | const input = Input.InClass(
137 | `@Future(callout=true) public static void method() {}`
138 | );
139 | const tokens = await tokenize(input);
140 |
141 | tokens.should.deep.equal([
142 | Token.Keywords.Modifiers.AnnotationName('@Future'),
143 | Token.Punctuation.OpenParen,
144 | Token.Variables.ReadWrite('callout'),
145 | Token.Operators.Assignment,
146 | Token.Literals.Boolean.True,
147 | Token.Punctuation.CloseParen,
148 | Token.Keywords.Modifiers.Public,
149 | Token.Keywords.Modifiers.Static,
150 | Token.PrimitiveType.Void,
151 | Token.Identifiers.MethodName('method'),
152 | Token.Punctuation.OpenParen,
153 | Token.Punctuation.CloseParen,
154 | Token.Punctuation.OpenBrace,
155 | Token.Punctuation.CloseBrace,
156 | ]);
157 | });
158 | });
159 | });
160 |
--------------------------------------------------------------------------------
/test/literals.test.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Modifications Copyright (c) 2018 Salesforce.
4 | * See LICENSE in the project root for license information.
5 | *--------------------------------------------------------------------------------------------*/
6 |
7 | import { should } from 'chai';
8 | import { tokenize, Input, Token } from './utils/tokenize';
9 |
10 | describe('Grammar', () => {
11 | before(() => {
12 | should();
13 | });
14 |
15 | describe('Literals', () => {
16 | describe('Booleans', () => {
17 | it('true', async () => {
18 | const input = Input.InClass(`Boolean x = true;`);
19 | const tokens = await tokenize(input);
20 |
21 | tokens.should.deep.equal([
22 | Token.PrimitiveType.Boolean,
23 | Token.Identifiers.FieldName('x'),
24 | Token.Operators.Assignment,
25 | Token.Literals.Boolean.True,
26 | Token.Punctuation.Semicolon,
27 | ]);
28 | });
29 |
30 | it('false', async () => {
31 | const input = Input.InClass(`Boolean x = false;`);
32 | const tokens = await tokenize(input);
33 |
34 | tokens.should.deep.equal([
35 | Token.PrimitiveType.Boolean,
36 | Token.Identifiers.FieldName('x'),
37 | Token.Operators.Assignment,
38 | Token.Literals.Boolean.False,
39 | Token.Punctuation.Semicolon,
40 | ]);
41 | });
42 | });
43 |
44 | describe('Chars', () => {
45 | it('empty', async () => {
46 | const input = Input.InMethod(`String x = '';`);
47 | const tokens = await tokenize(input);
48 |
49 | tokens.should.deep.equal([
50 | Token.PrimitiveType.String,
51 | Token.Identifiers.LocalName('x'),
52 | Token.Operators.Assignment,
53 | Token.Punctuation.String.Begin,
54 | Token.Punctuation.String.End,
55 | Token.Punctuation.Semicolon,
56 | ]);
57 | });
58 |
59 | it('letter', async () => {
60 | const input = Input.InMethod(`String x = 'a';`);
61 | const tokens = await tokenize(input);
62 |
63 | tokens.should.deep.equal([
64 | Token.PrimitiveType.String,
65 | Token.Identifiers.LocalName('x'),
66 | Token.Operators.Assignment,
67 | Token.Punctuation.String.Begin,
68 | Token.Literals.String('a'),
69 | Token.Punctuation.String.End,
70 | Token.Punctuation.Semicolon,
71 | ]);
72 | });
73 |
74 | it('escaped single quote', async () => {
75 | const input = Input.InMethod(`String x = '\\'';`);
76 | const tokens = await tokenize(input);
77 |
78 | tokens.should.deep.equal([
79 | Token.PrimitiveType.String,
80 | Token.Identifiers.LocalName('x'),
81 | Token.Operators.Assignment,
82 | Token.Punctuation.String.Begin,
83 | Token.Literals.CharacterEscape("\\'"),
84 | Token.Punctuation.String.End,
85 | Token.Punctuation.Semicolon,
86 | ]);
87 | });
88 | });
89 |
90 | describe('Numbers', () => {
91 | it('decimal zero', async () => {
92 | const input = Input.InClass(`Integer x = 0;`);
93 | const tokens = await tokenize(input);
94 |
95 | tokens.should.deep.equal([
96 | Token.PrimitiveType.Integer,
97 | Token.Identifiers.FieldName('x'),
98 | Token.Operators.Assignment,
99 | Token.Literals.Numeric.Decimal('0'),
100 | Token.Punctuation.Semicolon,
101 | ]);
102 | });
103 |
104 | it('hexadecimal zero', async () => {
105 | const input = Input.InClass(`Integer x = 0x0;`);
106 | const tokens = await tokenize(input);
107 |
108 | tokens.should.deep.equal([
109 | Token.PrimitiveType.Integer,
110 | Token.Identifiers.FieldName('x'),
111 | Token.Operators.Assignment,
112 | Token.Literals.Numeric.Hexadecimal('0x0'),
113 | Token.Punctuation.Semicolon,
114 | ]);
115 | });
116 |
117 | it('binary zero', async () => {
118 | const input = Input.InClass(`Integer x = 0b0;`);
119 | const tokens = await tokenize(input);
120 |
121 | tokens.should.deep.equal([
122 | Token.PrimitiveType.Integer,
123 | Token.Identifiers.FieldName('x'),
124 | Token.Operators.Assignment,
125 | Token.Literals.Numeric.Binary('0b0'),
126 | Token.Punctuation.Semicolon,
127 | ]);
128 | });
129 |
130 | it('Double zero', async () => {
131 | const input = Input.InClass(`Double x = 0.0;`);
132 | const tokens = await tokenize(input);
133 |
134 | tokens.should.deep.equal([
135 | Token.PrimitiveType.Double,
136 | Token.Identifiers.FieldName('x'),
137 | Token.Operators.Assignment,
138 | Token.Literals.Numeric.Decimal('0.0'),
139 | Token.Punctuation.Semicolon,
140 | ]);
141 | });
142 | });
143 |
144 | describe('Strings', () => {
145 | it('simple', async () => {
146 | const input = Input.InClass(`String test = 'hello world!';`);
147 | const tokens = await tokenize(input);
148 |
149 | tokens.should.deep.equal([
150 | Token.PrimitiveType.String,
151 | Token.Identifiers.FieldName('test'),
152 | Token.Operators.Assignment,
153 | Token.Punctuation.String.Begin,
154 | Token.Literals.String('hello world!'),
155 | Token.Punctuation.String.End,
156 | Token.Punctuation.Semicolon,
157 | ]);
158 | });
159 |
160 | it('escaped double-quote', async () => {
161 | const input = Input.InClass(`String test = 'hello \\"world!\\"';`);
162 | const tokens = await tokenize(input);
163 |
164 | tokens.should.deep.equal([
165 | Token.PrimitiveType.String,
166 | Token.Identifiers.FieldName('test'),
167 | Token.Operators.Assignment,
168 | Token.Punctuation.String.Begin,
169 | Token.Literals.String('hello '),
170 | Token.Literals.CharacterEscape('\\"'),
171 | Token.Literals.String('world!'),
172 | Token.Literals.CharacterEscape('\\"'),
173 | Token.Punctuation.String.End,
174 | Token.Punctuation.Semicolon,
175 | ]);
176 | });
177 | });
178 | });
179 | });
180 |
--------------------------------------------------------------------------------
/test/enum.test.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Modifications Copyright (c) 2018 Salesforce.
4 | * See LICENSE in the project root for license information.
5 | *--------------------------------------------------------------------------------------------*/
6 |
7 | import { should } from 'chai';
8 | import { tokenize, Input, Token } from './utils/tokenize';
9 |
10 | describe('Grammar', () => {
11 | before(() => {
12 | should();
13 | });
14 |
15 | describe('Enums', () => {
16 | it('simple enum', async () => {
17 | const input = `enum E { }`;
18 | const tokens = await tokenize(input);
19 |
20 | tokens.should.deep.equal([
21 | Token.Keywords.Enum,
22 | Token.Identifiers.EnumName('E'),
23 | Token.Punctuation.OpenBrace,
24 | Token.Punctuation.CloseBrace,
25 | ]);
26 | });
27 |
28 | it('simple public enum', async () => {
29 | const input = `public enum Season {WINTER, SPRING, SUMMER, FALL}`;
30 | const tokens = await tokenize(input);
31 |
32 | tokens.should.deep.equal([
33 | Token.Keywords.Modifiers.Public,
34 | Token.Keywords.Enum,
35 | Token.Identifiers.EnumName('Season'),
36 | Token.Punctuation.OpenBrace,
37 | Token.Identifiers.EnumMemberName('WINTER'),
38 | Token.Punctuation.Comma,
39 | Token.Identifiers.EnumMemberName('SPRING'),
40 | Token.Punctuation.Comma,
41 | Token.Identifiers.EnumMemberName('SUMMER'),
42 | Token.Punctuation.Comma,
43 | Token.Identifiers.EnumMemberName('FALL'),
44 | Token.Punctuation.CloseBrace,
45 | ]);
46 | });
47 |
48 | it('internal public enum', async () => {
49 | const input = Input.InClass(
50 | `public enum Season {WINTER, SPRING, SUMMER, FALL}`
51 | );
52 | const tokens = await tokenize(input);
53 |
54 | tokens.should.deep.equal([
55 | Token.Keywords.Modifiers.Public,
56 | Token.Keywords.Enum,
57 | Token.Identifiers.EnumName('Season'),
58 | Token.Punctuation.OpenBrace,
59 | Token.Identifiers.EnumMemberName('WINTER'),
60 | Token.Punctuation.Comma,
61 | Token.Identifiers.EnumMemberName('SPRING'),
62 | Token.Punctuation.Comma,
63 | Token.Identifiers.EnumMemberName('SUMMER'),
64 | Token.Punctuation.Comma,
65 | Token.Identifiers.EnumMemberName('FALL'),
66 | Token.Punctuation.CloseBrace,
67 | ]);
68 | });
69 |
70 | it('enum with single member', async () => {
71 | const input = `enum E { M1 }`;
72 | const tokens = await tokenize(input);
73 |
74 | tokens.should.deep.equal([
75 | Token.Keywords.Enum,
76 | Token.Identifiers.EnumName('E'),
77 | Token.Punctuation.OpenBrace,
78 | Token.Identifiers.EnumMemberName('M1'),
79 | Token.Punctuation.CloseBrace,
80 | ]);
81 | });
82 |
83 | it('enum with multiple members', async () => {
84 | const input = `enum Color { Red, Green, Blue }`;
85 | const tokens = await tokenize(input);
86 |
87 | tokens.should.deep.equal([
88 | Token.Keywords.Enum,
89 | Token.Identifiers.EnumName('Color'),
90 | Token.Punctuation.OpenBrace,
91 | Token.Identifiers.EnumMemberName('Red'),
92 | Token.Punctuation.Comma,
93 | Token.Identifiers.EnumMemberName('Green'),
94 | Token.Punctuation.Comma,
95 | Token.Identifiers.EnumMemberName('Blue'),
96 | Token.Punctuation.CloseBrace,
97 | ]);
98 | });
99 |
100 | it('enum with initialized member', async () => {
101 | const input = `
102 | enum E
103 | {
104 | Value1 = 1,
105 | Value2,
106 | Value3
107 | }
108 | `;
109 |
110 | const tokens = await tokenize(input);
111 |
112 | tokens.should.deep.equal([
113 | Token.Keywords.Enum,
114 | Token.Identifiers.EnumName('E'),
115 | Token.Punctuation.OpenBrace,
116 | Token.Identifiers.EnumMemberName('Value1'),
117 | Token.Operators.Assignment,
118 | Token.Literals.Numeric.Decimal('1'),
119 | Token.Punctuation.Comma,
120 | Token.Identifiers.EnumMemberName('Value2'),
121 | Token.Punctuation.Comma,
122 | Token.Identifiers.EnumMemberName('Value3'),
123 | Token.Punctuation.CloseBrace,
124 | ]);
125 | });
126 |
127 | it('enum members are highligted properly (issue omnisharp-vscode#1108)', async () => {
128 | const input = `
129 | public enum TestEnum
130 | {
131 | enum1,
132 | enum2,
133 | enum3,
134 | enum4
135 | }
136 |
137 | public class TestClass
138 | {
139 |
140 | }
141 |
142 | public enum TestEnum2
143 | {
144 | enum1 = 10,
145 | enum2 = 15,
146 | }
147 |
148 | public class TestClass2
149 | {
150 |
151 | }
152 | `;
153 |
154 | const tokens = await tokenize(input);
155 |
156 | tokens.should.deep.equal([
157 | Token.Keywords.Modifiers.Public,
158 | Token.Keywords.Enum,
159 | Token.Identifiers.EnumName('TestEnum'),
160 | Token.Punctuation.OpenBrace,
161 | Token.Identifiers.EnumMemberName('enum1'),
162 | Token.Punctuation.Comma,
163 | Token.Identifiers.EnumMemberName('enum2'),
164 | Token.Punctuation.Comma,
165 | Token.Identifiers.EnumMemberName('enum3'),
166 | Token.Punctuation.Comma,
167 | Token.Identifiers.EnumMemberName('enum4'),
168 | Token.Punctuation.CloseBrace,
169 |
170 | Token.Keywords.Modifiers.Public,
171 | Token.Keywords.Class,
172 | Token.Identifiers.ClassName('TestClass'),
173 | Token.Punctuation.OpenBrace,
174 | Token.Punctuation.CloseBrace,
175 |
176 | Token.Keywords.Modifiers.Public,
177 | Token.Keywords.Enum,
178 | Token.Identifiers.EnumName('TestEnum2'),
179 | Token.Punctuation.OpenBrace,
180 | Token.Identifiers.EnumMemberName('enum1'),
181 | Token.Operators.Assignment,
182 | Token.Literals.Numeric.Decimal('10'),
183 | Token.Punctuation.Comma,
184 | Token.Identifiers.EnumMemberName('enum2'),
185 | Token.Operators.Assignment,
186 | Token.Literals.Numeric.Decimal('15'),
187 | Token.Punctuation.Comma,
188 | Token.Punctuation.CloseBrace,
189 |
190 | Token.Keywords.Modifiers.Public,
191 | Token.Keywords.Class,
192 | Token.Identifiers.ClassName('TestClass2'),
193 | Token.Punctuation.OpenBrace,
194 | Token.Punctuation.CloseBrace,
195 | ]);
196 | });
197 | });
198 | });
199 |
--------------------------------------------------------------------------------
/test/constructor.test.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Modifications Copyright (c) 2018 Salesforce.
4 | * See LICENSE in the project root for license information.
5 | *--------------------------------------------------------------------------------------------*/
6 |
7 | import { should } from 'chai';
8 | import { tokenize, Input, Token } from './utils/tokenize';
9 |
10 | describe('Grammar', () => {
11 | before(() => {
12 | should();
13 | });
14 |
15 | describe('Constructors', () => {
16 | it('instance constructor with no parameters', async () => {
17 | const input = Input.InClass(`TestClass() { }`);
18 | const tokens = await tokenize(input);
19 |
20 | tokens.should.deep.equal([
21 | Token.Identifiers.MethodName('TestClass'),
22 | Token.Punctuation.OpenParen,
23 | Token.Punctuation.CloseParen,
24 | Token.Punctuation.OpenBrace,
25 | Token.Punctuation.CloseBrace,
26 | ]);
27 | });
28 |
29 | it('public instance constructor with no parameters', async () => {
30 | const input = Input.InClass(`public TestClass() { }`);
31 | const tokens = await tokenize(input);
32 |
33 | tokens.should.deep.equal([
34 | Token.Keywords.Modifiers.Public,
35 | Token.Identifiers.MethodName('TestClass'),
36 | Token.Punctuation.OpenParen,
37 | Token.Punctuation.CloseParen,
38 | Token.Punctuation.OpenBrace,
39 | Token.Punctuation.CloseBrace,
40 | ]);
41 | });
42 |
43 | it('public instance constructor with one parameter', async () => {
44 | const input = Input.InClass(`public TestClass(Integer x) { }`);
45 | const tokens = await tokenize(input);
46 |
47 | tokens.should.deep.equal([
48 | Token.Keywords.Modifiers.Public,
49 | Token.Identifiers.MethodName('TestClass'),
50 | Token.Punctuation.OpenParen,
51 | Token.PrimitiveType.Integer,
52 | Token.Identifiers.ParameterName('x'),
53 | Token.Punctuation.CloseParen,
54 | Token.Punctuation.OpenBrace,
55 | Token.Punctuation.CloseBrace,
56 | ]);
57 | });
58 |
59 | it('public instance constructor with one ref parameter', async () => {
60 | const input = Input.InClass(`public TestClass(Object x) { }`);
61 | const tokens = await tokenize(input);
62 |
63 | tokens.should.deep.equal([
64 | Token.Keywords.Modifiers.Public,
65 | Token.Identifiers.MethodName('TestClass'),
66 | Token.Punctuation.OpenParen,
67 | Token.PrimitiveType.Object,
68 | Token.Identifiers.ParameterName('x'),
69 | Token.Punctuation.CloseParen,
70 | Token.Punctuation.OpenBrace,
71 | Token.Punctuation.CloseBrace,
72 | ]);
73 | });
74 |
75 | it('instance constructor with two parameters', async () => {
76 | const input = Input.InClass(`
77 | TestClass(String x, Integer y)
78 | {
79 | }`);
80 | const tokens = await tokenize(input);
81 |
82 | tokens.should.deep.equal([
83 | Token.Identifiers.MethodName('TestClass'),
84 | Token.Punctuation.OpenParen,
85 | Token.PrimitiveType.String,
86 | Token.Identifiers.ParameterName('x'),
87 | Token.Punctuation.Comma,
88 | Token.PrimitiveType.Integer,
89 | Token.Identifiers.ParameterName('y'),
90 | Token.Punctuation.CloseParen,
91 | Token.Punctuation.OpenBrace,
92 | Token.Punctuation.CloseBrace,
93 | ]);
94 | });
95 |
96 | it('static constructor no parameters', async () => {
97 | const input = Input.InClass(`TestClass() { }`);
98 | const tokens = await tokenize(input);
99 |
100 | tokens.should.deep.equal([
101 | Token.Identifiers.MethodName('TestClass'),
102 | Token.Punctuation.OpenParen,
103 | Token.Punctuation.CloseParen,
104 | Token.Punctuation.OpenBrace,
105 | Token.Punctuation.CloseBrace,
106 | ]);
107 | });
108 |
109 | it('Open multiline comment in front of parameter highlights properly (issue omnisharp-vscode#861)', async () => {
110 | const input = Input.InClass(`
111 | WaitHandle(Task self)
112 | {
113 | this.task = self;
114 | }
115 | `);
116 | const tokens = await tokenize(input);
117 |
118 | tokens.should.deep.equal([
119 | Token.Identifiers.MethodName('WaitHandle'),
120 | Token.Punctuation.OpenParen,
121 | Token.Type('Task'),
122 | Token.Identifiers.ParameterName('self'),
123 | Token.Punctuation.CloseParen,
124 | Token.Punctuation.OpenBrace,
125 | Token.Keywords.This,
126 | Token.Punctuation.Accessor,
127 | Token.Variables.Property('task'),
128 | Token.Operators.Assignment,
129 | Token.Variables.ReadWrite('self'),
130 | Token.Punctuation.Semicolon,
131 | Token.Punctuation.CloseBrace,
132 | ]);
133 | });
134 |
135 | it('closing parenthesis of parameter list on next line', async () => {
136 | const input = Input.InClass(`
137 | public C(
138 | String s
139 | )
140 | {
141 | }`);
142 | const tokens = await tokenize(input);
143 |
144 | tokens.should.deep.equal([
145 | Token.Keywords.Modifiers.Public,
146 | Token.Identifiers.MethodName('C'),
147 | Token.Punctuation.OpenParen,
148 |
149 | Token.PrimitiveType.String,
150 | Token.Identifiers.ParameterName('s'),
151 |
152 | Token.Punctuation.CloseParen,
153 | Token.Punctuation.OpenBrace,
154 | Token.Punctuation.CloseBrace,
155 | ]);
156 | });
157 |
158 | it('closing parenthesis of parameter list on next line (issue #88)', async () => {
159 | const input = Input.InClass(`
160 | public AccountController(
161 | UserManager userManager,
162 | SignInManager signInManager,
163 | ILogger logger
164 | )
165 | {
166 | }`);
167 | const tokens = await tokenize(input);
168 |
169 | tokens.should.deep.equal([
170 | Token.Keywords.Modifiers.Public,
171 | Token.Identifiers.MethodName('AccountController'),
172 | Token.Punctuation.OpenParen,
173 |
174 | Token.Type('UserManager'),
175 | Token.Punctuation.TypeParameters.Begin,
176 | Token.Type('User'),
177 | Token.Punctuation.TypeParameters.End,
178 | Token.Identifiers.ParameterName('userManager'),
179 | Token.Punctuation.Comma,
180 |
181 | Token.Type('SignInManager'),
182 | Token.Punctuation.TypeParameters.Begin,
183 | Token.Type('User'),
184 | Token.Punctuation.TypeParameters.End,
185 | Token.Identifiers.ParameterName('signInManager'),
186 | Token.Punctuation.Comma,
187 |
188 | Token.Type('ILogger'),
189 | Token.Punctuation.TypeParameters.Begin,
190 | Token.Type('AccountController'),
191 | Token.Punctuation.TypeParameters.End,
192 | Token.Identifiers.ParameterName('logger'),
193 |
194 | Token.Punctuation.CloseParen,
195 | Token.Punctuation.OpenBrace,
196 | Token.Punctuation.CloseBrace,
197 | ]);
198 | });
199 | });
200 | });
201 |
--------------------------------------------------------------------------------
/test/initializer-block.test.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Modifications Copyright (c) 2018 Salesforce.
4 | * See LICENSE in the project root for license information.
5 | *--------------------------------------------------------------------------------------------*/
6 |
7 | import { should } from 'chai';
8 | import { tokenize, Input, Token } from './utils/tokenize';
9 |
10 | describe('Grammar', () => {
11 | before(() => {
12 | should();
13 | });
14 |
15 | describe('Initializer Blocks', () => {
16 | it('empty initialization block', async () => {
17 | const input = Input.InClass(`{ }`);
18 | const tokens = await tokenize(input);
19 |
20 | tokens.should.deep.equal([
21 | Token.Punctuation.OpenBrace,
22 | Token.Punctuation.CloseBrace,
23 | ]);
24 | });
25 |
26 | it('initialization block with method call and string literal', async () => {
27 | const input = Input.InClass(`
28 | {
29 | this.setMessage('Object graph should be a Directed Acyclic Graph.');
30 | }`);
31 | const tokens = await tokenize(input);
32 |
33 | tokens.should.deep.equal([
34 | Token.Punctuation.OpenBrace,
35 | Token.Keywords.This,
36 | Token.Punctuation.Accessor,
37 | Token.Identifiers.MethodName('setMessage'),
38 | Token.Punctuation.OpenParen,
39 | Token.Punctuation.String.Begin,
40 | Token.Literals.String(
41 | 'Object graph should be a Directed Acyclic Graph.'
42 | ),
43 | Token.Punctuation.String.End,
44 | Token.Punctuation.CloseParen,
45 | Token.Punctuation.Semicolon,
46 | Token.Punctuation.CloseBrace,
47 | ]);
48 | });
49 |
50 | it('initialization block with multiple statements', async () => {
51 | const input = Input.InClass(`
52 | {
53 | Integer x = 5;
54 | String message = 'test';
55 | this.setMessage(message);
56 | }`);
57 | const tokens = await tokenize(input);
58 |
59 | tokens.should.deep.equal([
60 | Token.Punctuation.OpenBrace,
61 | Token.PrimitiveType.Integer,
62 | Token.Identifiers.LocalName('x'),
63 | Token.Operators.Assignment,
64 | Token.Literals.Numeric.Decimal('5'),
65 | Token.Punctuation.Semicolon,
66 | Token.PrimitiveType.String,
67 | Token.Identifiers.LocalName('message'),
68 | Token.Operators.Assignment,
69 | Token.Punctuation.String.Begin,
70 | Token.Literals.String('test'),
71 | Token.Punctuation.String.End,
72 | Token.Punctuation.Semicolon,
73 | Token.Keywords.This,
74 | Token.Punctuation.Accessor,
75 | Token.Identifiers.MethodName('setMessage'),
76 | Token.Punctuation.OpenParen,
77 | Token.Variables.ReadWrite('message'),
78 | Token.Punctuation.CloseParen,
79 | Token.Punctuation.Semicolon,
80 | Token.Punctuation.CloseBrace,
81 | ]);
82 | });
83 |
84 | it('initialization block in nested class (issue #4920)', async () => {
85 | const input = Input.FromText(`
86 | public class TestDataBuilder {
87 | public class NoneDAGException extends Exception {
88 | // Initializer
89 | {
90 | this.setMessage('Object graph should be a Directed Acyclic Graph.');
91 | }
92 |
93 | // Sample method for comparison
94 | public void anotherMethod() {
95 | this.setMessage('Object graph should be a Directed Acyclic Graph.');
96 | }
97 | }
98 | }`);
99 | const tokens = await tokenize(input);
100 |
101 | // Find the initialization block tokens (should start after the comment)
102 | const initBlockStart = tokens.findIndex(
103 | (t, i) =>
104 | i > 0 &&
105 | tokens[i - 1].text === '//' &&
106 | tokens[i].text === 'Initializer'
107 | );
108 | const initBlockEnd = tokens.findIndex(
109 | (t, i) =>
110 | i > initBlockStart && t.text === '}' && tokens[i - 1]?.text === ';'
111 | );
112 |
113 | // Extract tokens for the initialization block
114 | const initBlockTokens = tokens.slice(
115 | initBlockStart + 3, // Skip comment tokens
116 | initBlockEnd + 1
117 | );
118 |
119 | // Verify initialization block has proper string highlighting
120 | initBlockTokens.should.include.deep.members([
121 | Token.Punctuation.OpenBrace,
122 | Token.Keywords.This,
123 | Token.Punctuation.Accessor,
124 | Token.Identifiers.MethodName('setMessage'),
125 | Token.Punctuation.OpenParen,
126 | Token.Punctuation.String.Begin,
127 | Token.Literals.String(
128 | 'Object graph should be a Directed Acyclic Graph.'
129 | ),
130 | Token.Punctuation.String.End,
131 | Token.Punctuation.CloseParen,
132 | Token.Punctuation.Semicolon,
133 | Token.Punctuation.CloseBrace,
134 | ]);
135 | });
136 |
137 | it('initialization block syntax highlighting matches method body', async () => {
138 | const input = Input.InClass(`
139 | {
140 | this.setMessage('test');
141 | }
142 |
143 | public void testMethod() {
144 | this.setMessage('test');
145 | }`);
146 | const tokens = await tokenize(input);
147 |
148 | // Find initialization block tokens
149 | const initStart = tokens.findIndex((t) => t.text === '{');
150 | const initEnd = tokens.findIndex(
151 | (t, i) => i > initStart && t.text === '}' && tokens[i - 1]?.text === ';'
152 | );
153 | const initTokens = tokens.slice(initStart, initEnd + 1);
154 |
155 | // Find method body tokens
156 | const methodStart = tokens.findIndex(
157 | (t, i) => i > initEnd && tokens[i - 1]?.text === ')' && t.text === '{'
158 | );
159 | const methodEnd = tokens.findIndex(
160 | (t, i) =>
161 | i > methodStart && t.text === '}' && tokens[i - 1]?.text === ';'
162 | );
163 | const methodTokens = tokens.slice(methodStart, methodEnd + 1);
164 |
165 | // Both should have the same highlighting for the string literal
166 | const initStringTokens = initTokens.filter(
167 | (t) => t.type === 'string.quoted.single.apex'
168 | );
169 | const methodStringTokens = methodTokens.filter(
170 | (t) => t.type === 'string.quoted.single.apex'
171 | );
172 |
173 | initStringTokens.length.should.be.greaterThan(0);
174 | methodStringTokens.length.should.be.greaterThan(0);
175 | initStringTokens[0].type.should.equal(methodStringTokens[0].type);
176 | });
177 |
178 | it('static keyword before block is handled (even though static blocks are not valid Apex)', async () => {
179 | // Note: Apex does NOT support static initialization blocks like Java
180 | // This test verifies the grammar handles the syntax correctly for highlighting
181 | // even though it's not valid Apex code
182 | const input = Input.InClass(`
183 | static {
184 | Integer x = 5;
185 | }`);
186 | const tokens = await tokenize(input);
187 |
188 | // The static keyword should be matched, then the block should be matched
189 | tokens.should.include.deep.members([
190 | Token.Keywords.Modifiers.Static,
191 | Token.Punctuation.OpenBrace,
192 | Token.PrimitiveType.Integer,
193 | Token.Identifiers.LocalName('x'),
194 | Token.Operators.Assignment,
195 | Token.Literals.Numeric.Decimal('5'),
196 | Token.Punctuation.Semicolon,
197 | Token.Punctuation.CloseBrace,
198 | ]);
199 | });
200 | });
201 | });
202 |
--------------------------------------------------------------------------------
/test/xml-doc-comment.test.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Modifications Copyright (c) 2018 Salesforce.
4 | * See LICENSE in the project root for license information.
5 | *--------------------------------------------------------------------------------------------*/
6 |
7 | import { should } from 'chai';
8 | import { tokenize, Input, Token } from './utils/tokenize';
9 |
10 | describe('Grammar', () => {
11 | before(() => {
12 | should();
13 | });
14 |
15 | describe('XML Doc Comments', () => {
16 | it('start tag', async () => {
17 | const input = `/// `;
18 | const tokens = await tokenize(input);
19 |
20 | tokens.should.deep.equal([
21 | Token.XmlDocComments.Begin,
22 | Token.XmlDocComments.Text(' '),
23 | Token.XmlDocComments.Tag.StartTagBegin,
24 | Token.XmlDocComments.Tag.Name('summary'),
25 | Token.XmlDocComments.Tag.StartTagEnd,
26 | ]);
27 | });
28 |
29 | it('end tag', async () => {
30 | const input = `/// `;
31 | const tokens = await tokenize(input);
32 |
33 | tokens.should.deep.equal([
34 | Token.XmlDocComments.Begin,
35 | Token.XmlDocComments.Text(' '),
36 | Token.XmlDocComments.Tag.EndTagBegin,
37 | Token.XmlDocComments.Tag.Name('summary'),
38 | Token.XmlDocComments.Tag.EndTagEnd,
39 | ]);
40 | });
41 |
42 | it('empty tag', async () => {
43 | const input = `/// `;
44 | const tokens = await tokenize(input);
45 |
46 | tokens.should.deep.equal([
47 | Token.XmlDocComments.Begin,
48 | Token.XmlDocComments.Text(' '),
49 | Token.XmlDocComments.Tag.EmptyTagBegin,
50 | Token.XmlDocComments.Tag.Name('summary'),
51 | Token.XmlDocComments.Tag.EmptyTagEnd,
52 | ]);
53 | });
54 |
55 | it('start tag with attribute and single-quoted string', async () => {
56 | const input = `/// `;
57 | const tokens = await tokenize(input);
58 |
59 | tokens.should.deep.equal([
60 | Token.XmlDocComments.Begin,
61 | Token.XmlDocComments.Text(' '),
62 | Token.XmlDocComments.Tag.StartTagBegin,
63 | Token.XmlDocComments.Tag.Name('param'),
64 | Token.XmlDocComments.Attribute.Name('name'),
65 | Token.XmlDocComments.Equals,
66 | Token.XmlDocComments.String.SingleQuoted.Begin,
67 | Token.XmlDocComments.String.SingleQuoted.Text('x'),
68 | Token.XmlDocComments.String.SingleQuoted.End,
69 | Token.XmlDocComments.Tag.StartTagEnd,
70 | ]);
71 | });
72 |
73 | it('start tag with attribute and double-quoted string', async () => {
74 | const input = `/// `;
75 | const tokens = await tokenize(input);
76 |
77 | tokens.should.deep.equal([
78 | Token.XmlDocComments.Begin,
79 | Token.XmlDocComments.Text(' '),
80 | Token.XmlDocComments.Tag.StartTagBegin,
81 | Token.XmlDocComments.Tag.Name('param'),
82 | Token.XmlDocComments.Attribute.Name('name'),
83 | Token.XmlDocComments.Equals,
84 | Token.XmlDocComments.String.DoubleQuoted.Begin,
85 | Token.XmlDocComments.String.DoubleQuoted.Text('x'),
86 | Token.XmlDocComments.String.DoubleQuoted.End,
87 | Token.XmlDocComments.Tag.StartTagEnd,
88 | ]);
89 | });
90 |
91 | it('comment', async () => {
92 | const input = `/// `;
93 | const tokens = await tokenize(input);
94 |
95 | tokens.should.deep.equal([
96 | Token.XmlDocComments.Begin,
97 | Token.XmlDocComments.Text(' '),
98 | Token.XmlDocComments.Comment.Begin,
99 | Token.XmlDocComments.Comment.Text(' comment '),
100 | Token.XmlDocComments.Comment.End,
101 | ]);
102 | });
103 |
104 | it('cdata', async () => {
105 | const input = `/// `;
106 | const tokens = await tokenize(input);
107 |
108 | tokens.should.deep.equal([
109 | Token.XmlDocComments.Begin,
110 | Token.XmlDocComments.Text(' '),
111 | Token.XmlDocComments.CData.Begin,
112 | Token.XmlDocComments.CData.Text('c'),
113 | Token.XmlDocComments.CData.End,
114 | ]);
115 | });
116 |
117 | it('character entity - name', async () => {
118 | const input = `/// &`;
119 | const tokens = await tokenize(input);
120 |
121 | tokens.should.deep.equal([
122 | Token.XmlDocComments.Begin,
123 | Token.XmlDocComments.Text(' '),
124 | Token.XmlDocComments.CharacterEntity.Begin,
125 | Token.XmlDocComments.CharacterEntity.Text('amp'),
126 | Token.XmlDocComments.CharacterEntity.End,
127 | ]);
128 | });
129 |
130 | it('character entity - decimal', async () => {
131 | const input = `/// &`;
132 | const tokens = await tokenize(input);
133 |
134 | tokens.should.deep.equal([
135 | Token.XmlDocComments.Begin,
136 | Token.XmlDocComments.Text(' '),
137 | Token.XmlDocComments.CharacterEntity.Begin,
138 | Token.XmlDocComments.CharacterEntity.Text('#0038'),
139 | Token.XmlDocComments.CharacterEntity.End,
140 | ]);
141 | });
142 |
143 | it('character entity - hdex', async () => {
144 | const input = `/// &`;
145 | const tokens = await tokenize(input);
146 |
147 | tokens.should.deep.equal([
148 | Token.XmlDocComments.Begin,
149 | Token.XmlDocComments.Text(' '),
150 | Token.XmlDocComments.CharacterEntity.Begin,
151 | Token.XmlDocComments.CharacterEntity.Text('#x0026'),
152 | Token.XmlDocComments.CharacterEntity.End,
153 | ]);
154 | });
155 |
156 | it('XML doc comments are highlighted properly on enum members (issue omnisharp-vscode#706)', async () => {
157 | const input = `
158 | /// This is a test Enum
159 | public enum TestEnum
160 | {
161 | /// Test Value One
162 | TestValueOne= 0,
163 | /// Test Value Two
164 | TestValueTwo = 1
165 | }`;
166 |
167 | const tokens = await tokenize(input);
168 |
169 | tokens.should.deep.equal([
170 | Token.XmlDocComments.Begin,
171 | Token.XmlDocComments.Text(' '),
172 | Token.XmlDocComments.Tag.StartTagBegin,
173 | Token.XmlDocComments.Tag.Name('summary'),
174 | Token.XmlDocComments.Tag.StartTagEnd,
175 | Token.XmlDocComments.Text(' This is a test Enum '),
176 | Token.XmlDocComments.Tag.EndTagBegin,
177 | Token.XmlDocComments.Tag.Name('summary'),
178 | Token.XmlDocComments.Tag.EndTagEnd,
179 | Token.Keywords.Modifiers.Public,
180 | Token.Keywords.Enum,
181 | Token.Identifiers.EnumName('TestEnum'),
182 | Token.Punctuation.OpenBrace,
183 | Token.Comment.LeadingWhitespace(' '),
184 | Token.XmlDocComments.Begin,
185 | Token.XmlDocComments.Text(' '),
186 | Token.XmlDocComments.Tag.StartTagBegin,
187 | Token.XmlDocComments.Tag.Name('summary'),
188 | Token.XmlDocComments.Tag.StartTagEnd,
189 | Token.XmlDocComments.Text(' Test Value One '),
190 | Token.XmlDocComments.Tag.EndTagBegin,
191 | Token.XmlDocComments.Tag.Name('summary'),
192 | Token.XmlDocComments.Tag.EndTagEnd,
193 | Token.Identifiers.EnumMemberName('TestValueOne'),
194 | Token.Operators.Assignment,
195 | Token.Literals.Numeric.Decimal('0'),
196 | Token.Punctuation.Comma,
197 | Token.Comment.LeadingWhitespace(' '),
198 | Token.XmlDocComments.Begin,
199 | Token.XmlDocComments.Text(' '),
200 | Token.XmlDocComments.Tag.StartTagBegin,
201 | Token.XmlDocComments.Tag.Name('summary'),
202 | Token.XmlDocComments.Tag.StartTagEnd,
203 | Token.XmlDocComments.Text(' Test Value Two '),
204 | Token.XmlDocComments.Tag.EndTagBegin,
205 | Token.XmlDocComments.Tag.Name('summary'),
206 | Token.XmlDocComments.Tag.EndTagEnd,
207 | Token.Identifiers.EnumMemberName('TestValueTwo'),
208 | Token.Operators.Assignment,
209 | Token.Literals.Numeric.Decimal('1'),
210 | Token.Punctuation.CloseBrace,
211 | ]);
212 | });
213 | });
214 | });
215 |
--------------------------------------------------------------------------------
/test/class.test.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Modifications Copyright (c) 2018 Salesforce.
4 | * See LICENSE in the project root for license information.
5 | *--------------------------------------------------------------------------------------------*/
6 |
7 | import { should } from 'chai';
8 | import { tokenize, Input, Token } from './utils/tokenize';
9 |
10 | describe('Grammar', () => {
11 | before(() => {
12 | should();
13 | });
14 |
15 | describe('Apex Class', () => {
16 | it('class keyword and storage modifiers', async () => {
17 | const input = Input.FromText(`
18 | public class PublicClass { }
19 | class DefaultClass { }
20 | protected class ProtectedClass { }
21 | global class DefaultGlobalClass { }
22 | public with sharing class PublicWithSharingClass { }
23 | without sharing class DefaultWithoutSharingClass { }
24 | public virtual class PublicVirtualClass { }
25 | public abstract class PublicAbstractClass { }
26 | abstract class DefaultAbstractClass { }`);
27 |
28 | const tokens = await tokenize(input);
29 |
30 | tokens.should.deep.equal([
31 | Token.Keywords.Modifiers.Public,
32 | Token.Keywords.Class,
33 | Token.Identifiers.ClassName('PublicClass'),
34 | Token.Punctuation.OpenBrace,
35 | Token.Punctuation.CloseBrace,
36 |
37 | Token.Keywords.Class,
38 | Token.Identifiers.ClassName('DefaultClass'),
39 | Token.Punctuation.OpenBrace,
40 | Token.Punctuation.CloseBrace,
41 |
42 | Token.Keywords.Modifiers.Protected,
43 | Token.Keywords.Class,
44 | Token.Identifiers.ClassName('ProtectedClass'),
45 | Token.Punctuation.OpenBrace,
46 | Token.Punctuation.CloseBrace,
47 |
48 | Token.Keywords.Modifiers.Global,
49 | Token.Keywords.Class,
50 | Token.Identifiers.ClassName('DefaultGlobalClass'),
51 | Token.Punctuation.OpenBrace,
52 | Token.Punctuation.CloseBrace,
53 |
54 | Token.Keywords.Modifiers.Public,
55 | Token.Keywords.Modifiers.WithSharing,
56 | Token.Keywords.Class,
57 | Token.Identifiers.ClassName('PublicWithSharingClass'),
58 | Token.Punctuation.OpenBrace,
59 | Token.Punctuation.CloseBrace,
60 |
61 | Token.Keywords.Modifiers.WithoutSharing,
62 | Token.Keywords.Class,
63 | Token.Identifiers.ClassName('DefaultWithoutSharingClass'),
64 | Token.Punctuation.OpenBrace,
65 | Token.Punctuation.CloseBrace,
66 |
67 | Token.Keywords.Modifiers.Public,
68 | Token.Keywords.Modifiers.Virtual,
69 | Token.Keywords.Class,
70 | Token.Identifiers.ClassName('PublicVirtualClass'),
71 | Token.Punctuation.OpenBrace,
72 | Token.Punctuation.CloseBrace,
73 |
74 | Token.Keywords.Modifiers.Public,
75 | Token.Keywords.Modifiers.Abstract,
76 | Token.Keywords.Class,
77 | Token.Identifiers.ClassName('PublicAbstractClass'),
78 | Token.Punctuation.OpenBrace,
79 | Token.Punctuation.CloseBrace,
80 |
81 | Token.Keywords.Modifiers.Abstract,
82 | Token.Keywords.Class,
83 | Token.Identifiers.ClassName('DefaultAbstractClass'),
84 | Token.Punctuation.OpenBrace,
85 | Token.Punctuation.CloseBrace,
86 | ]);
87 | });
88 |
89 | it('public class with sharing', async () => {
90 | const input = Input.FromText(`public with sharing class C {}`);
91 | const tokens = await tokenize(input);
92 |
93 | tokens.should.deep.equal([
94 | Token.Keywords.Modifiers.Public,
95 | Token.Keywords.Modifiers.WithSharing,
96 | Token.Keywords.Class,
97 | Token.Identifiers.ClassName('C'),
98 | Token.Punctuation.OpenBrace,
99 | Token.Punctuation.CloseBrace,
100 | ]);
101 | });
102 |
103 | it('public class without sharing', async () => {
104 | const input = Input.FromText(`public without sharing class Fireburn {}`);
105 | const tokens = await tokenize(input);
106 |
107 | tokens.should.deep.equal([
108 | Token.Keywords.Modifiers.Public,
109 | Token.Keywords.Modifiers.WithoutSharing,
110 | Token.Keywords.Class,
111 | Token.Identifiers.ClassName('Fireburn'),
112 | Token.Punctuation.OpenBrace,
113 | Token.Punctuation.CloseBrace,
114 | ]);
115 | });
116 |
117 | it('simple class', async () => {
118 | const input = Input.FromText(`private class SimpleClass {}`);
119 | const tokens = await tokenize(input);
120 |
121 | tokens.should.deep.equal([
122 | Token.Keywords.Modifiers.Private,
123 | Token.Keywords.Class,
124 | Token.Identifiers.ClassName('SimpleClass'),
125 | Token.Punctuation.OpenBrace,
126 | Token.Punctuation.CloseBrace,
127 | ]);
128 | });
129 |
130 | it('global class', async () => {
131 | const input = Input.FromText(`global class GlobalClass {}`);
132 | const tokens = await tokenize(input);
133 |
134 | tokens.should.deep.equal([
135 | Token.Keywords.Modifiers.Global,
136 | Token.Keywords.Class,
137 | Token.Identifiers.ClassName('GlobalClass'),
138 | Token.Punctuation.OpenBrace,
139 | Token.Punctuation.CloseBrace,
140 | ]);
141 | });
142 |
143 | it('private class extends', async () => {
144 | const input = Input.FromText(`private class Car extends Vehicle {}`);
145 | const tokens = await tokenize(input);
146 |
147 | tokens.should.deep.equal([
148 | Token.Keywords.Modifiers.Private,
149 | Token.Keywords.Class,
150 | Token.Identifiers.ClassName('Car'),
151 | Token.Keywords.Extends,
152 | Token.Identifiers.ExtendsName('Vehicle'),
153 | Token.Punctuation.OpenBrace,
154 | Token.Punctuation.CloseBrace,
155 | ]);
156 | });
157 |
158 | it('class extends implements', async () => {
159 | const input = Input.FromText(
160 | `public abstract class MySecondException extends Exception implements MyInterface {}`
161 | );
162 | const tokens = await tokenize(input);
163 |
164 | tokens.should.deep.equal([
165 | Token.Keywords.Modifiers.Public,
166 | Token.Keywords.Modifiers.Abstract,
167 | Token.Keywords.Class,
168 | Token.Identifiers.ClassName('MySecondException'),
169 | Token.Keywords.Extends,
170 | Token.Identifiers.ExtendsName('Exception'),
171 | Token.Keywords.Implements,
172 | Token.Identifiers.ImplementsName('MyInterface'),
173 | Token.Punctuation.OpenBrace,
174 | Token.Punctuation.CloseBrace,
175 | ]);
176 | });
177 |
178 | it('class implements extends', async () => {
179 | const input = Input.FromText(
180 | `public abstract class MySecondException implements MyInterface extends Exception {}`
181 | );
182 | const tokens = await tokenize(input);
183 |
184 | tokens.should.deep.equal([
185 | Token.Keywords.Modifiers.Public,
186 | Token.Keywords.Modifiers.Abstract,
187 | Token.Keywords.Class,
188 | Token.Identifiers.ClassName('MySecondException'),
189 | Token.Keywords.Implements,
190 | Token.Identifiers.ImplementsName('MyInterface'),
191 | Token.Keywords.Extends,
192 | Token.Identifiers.ExtendsName('Exception'),
193 | Token.Punctuation.OpenBrace,
194 | Token.Punctuation.CloseBrace,
195 | ]);
196 | });
197 |
198 | it('class implements multiple', async () => {
199 | const input = Input.FromText(
200 | `public abstract class MySecondException implements MyInterface, MyInterface2, MyInterface3 {}`
201 | );
202 | const tokens = await tokenize(input);
203 |
204 | tokens.should.deep.equal([
205 | Token.Keywords.Modifiers.Public,
206 | Token.Keywords.Modifiers.Abstract,
207 | Token.Keywords.Class,
208 | Token.Identifiers.ClassName('MySecondException'),
209 | Token.Keywords.Implements,
210 | Token.Identifiers.ImplementsName('MyInterface'),
211 | Token.Punctuation.Comma,
212 | Token.Identifiers.ImplementsName('MyInterface2'),
213 | Token.Punctuation.Comma,
214 | Token.Identifiers.ImplementsName('MyInterface3'),
215 | Token.Punctuation.OpenBrace,
216 | Token.Punctuation.CloseBrace,
217 | ]);
218 | });
219 |
220 | it('class extends namespace-qualified type (issue #50)', async () => {
221 | const input = Input.FromText(`class MyClass extends System.Exception {}`);
222 | const tokens = await tokenize(input);
223 |
224 | tokens.should.deep.equal([
225 | Token.Keywords.Class,
226 | Token.Identifiers.ClassName('MyClass'),
227 | Token.Keywords.Extends,
228 | Token.Support.Class.System,
229 | Token.Punctuation.Accessor,
230 | Token.Support.Class.TypeText('Exception'),
231 | Token.Punctuation.OpenBrace,
232 | Token.Punctuation.CloseBrace,
233 | ]);
234 | });
235 |
236 | it('class implements namespace-qualified type (issue #50)', async () => {
237 | const input = Input.FromText(
238 | `class MyClass implements Database.Batchable {}`
239 | );
240 | const tokens = await tokenize(input);
241 |
242 | tokens.should.deep.equal([
243 | Token.Keywords.Class,
244 | Token.Identifiers.ClassName('MyClass'),
245 | Token.Keywords.Implements,
246 | Token.Support.Class.Database,
247 | Token.Punctuation.Accessor,
248 | Token.Support.Class.TypeText('Batchable'),
249 | Token.Punctuation.TypeParameters.Begin,
250 | Token.Type('Account'),
251 | Token.Punctuation.TypeParameters.End,
252 | Token.Punctuation.OpenBrace,
253 | Token.Punctuation.CloseBrace,
254 | ]);
255 | });
256 | });
257 | });
258 |
--------------------------------------------------------------------------------
/test/for-statements.test.ts:
--------------------------------------------------------------------------------
1 | import { should } from 'chai';
2 | import { tokenize, Input, Token } from './utils/tokenize';
3 |
4 | describe('Grammar', () => {
5 | before(() => {
6 | should();
7 | });
8 |
9 | describe('For-Statements', () => {
10 | it('single-line for loop', async () => {
11 | const input = Input.InMethod(`for (Integer i = 0; i < 42; i++) { }`);
12 | const tokens = await tokenize(input);
13 |
14 | tokens.should.deep.equal([
15 | Token.Keywords.Control.For,
16 | Token.Punctuation.OpenParen,
17 | Token.PrimitiveType.Integer,
18 | Token.Identifiers.LocalName('i'),
19 | Token.Operators.Assignment,
20 | Token.Literals.Numeric.Decimal('0'),
21 | Token.Punctuation.Semicolon,
22 | Token.Variables.ReadWrite('i'),
23 | Token.Operators.Relational.LessThan,
24 | Token.Literals.Numeric.Decimal('42'),
25 | Token.Punctuation.Semicolon,
26 | Token.Variables.ReadWrite('i'),
27 | Token.Operators.Increment,
28 | Token.Punctuation.CloseParen,
29 | Token.Punctuation.OpenBrace,
30 | Token.Punctuation.CloseBrace,
31 | ]);
32 | });
33 |
34 | it('for loop with break', async () => {
35 | const input = Input.InMethod(`
36 | for (Integer i = 0; i < 42; i++)
37 | {
38 | break;
39 | }`);
40 | const tokens = await tokenize(input);
41 |
42 | tokens.should.deep.equal([
43 | Token.Keywords.Control.For,
44 | Token.Punctuation.OpenParen,
45 | Token.PrimitiveType.Integer,
46 | Token.Identifiers.LocalName('i'),
47 | Token.Operators.Assignment,
48 | Token.Literals.Numeric.Decimal('0'),
49 | Token.Punctuation.Semicolon,
50 | Token.Variables.ReadWrite('i'),
51 | Token.Operators.Relational.LessThan,
52 | Token.Literals.Numeric.Decimal('42'),
53 | Token.Punctuation.Semicolon,
54 | Token.Variables.ReadWrite('i'),
55 | Token.Operators.Increment,
56 | Token.Punctuation.CloseParen,
57 | Token.Punctuation.OpenBrace,
58 | Token.Keywords.Control.Break,
59 | Token.Punctuation.Semicolon,
60 | Token.Punctuation.CloseBrace,
61 | ]);
62 | });
63 |
64 | it('for loop with continue', async () => {
65 | const input = Input.InMethod(`
66 | for (Integer i = 0; i < 42; i++)
67 | {
68 | continue;
69 | }`);
70 | const tokens = await tokenize(input);
71 |
72 | tokens.should.deep.equal([
73 | Token.Keywords.Control.For,
74 | Token.Punctuation.OpenParen,
75 | Token.PrimitiveType.Integer,
76 | Token.Identifiers.LocalName('i'),
77 | Token.Operators.Assignment,
78 | Token.Literals.Numeric.Decimal('0'),
79 | Token.Punctuation.Semicolon,
80 | Token.Variables.ReadWrite('i'),
81 | Token.Operators.Relational.LessThan,
82 | Token.Literals.Numeric.Decimal('42'),
83 | Token.Punctuation.Semicolon,
84 | Token.Variables.ReadWrite('i'),
85 | Token.Operators.Increment,
86 | Token.Punctuation.CloseParen,
87 | Token.Punctuation.OpenBrace,
88 | Token.Keywords.Control.Continue,
89 | Token.Punctuation.Semicolon,
90 | Token.Punctuation.CloseBrace,
91 | ]);
92 | });
93 |
94 | it('for loop on collection', async () => {
95 | const input = Input.InMethod(`
96 | for (Integer i : listOfIntegers)
97 | {
98 | continue;
99 | }`);
100 | const tokens = await tokenize(input);
101 |
102 | tokens.should.deep.equal([
103 | Token.Keywords.Control.For,
104 | Token.Punctuation.OpenParen,
105 | Token.PrimitiveType.Integer,
106 | Token.Identifiers.LocalName('i'),
107 | Token.Keywords.Control.ColonIterator,
108 | Token.Variables.ReadWrite('listOfIntegers'),
109 | Token.Punctuation.CloseParen,
110 | Token.Punctuation.OpenBrace,
111 | Token.Keywords.Control.Continue,
112 | Token.Punctuation.Semicolon,
113 | Token.Punctuation.CloseBrace,
114 | ]);
115 | });
116 |
117 | it('for loop with a type, a query and a comment', async () => {
118 | const input = Input.InMethod(`
119 | for (Account a : [SELECT Id, Name FROM Account]){
120 | // break;
121 | }`);
122 | const tokens = await tokenize(input);
123 |
124 | tokens.should.deep.equal([
125 | Token.Keywords.Control.For,
126 | Token.Punctuation.OpenParen,
127 | Token.Type('Account'),
128 | Token.Identifiers.LocalName('a'),
129 | Token.Keywords.Control.ColonIterator,
130 | Token.Punctuation.OpenBracket,
131 | Token.Keywords.Queries.Select,
132 | Token.Keywords.Queries.FieldName('Id'),
133 | Token.Punctuation.Comma,
134 | Token.Keywords.Queries.FieldName('Name'),
135 | Token.Keywords.Queries.From,
136 | Token.Keywords.Queries.TypeName('Account'),
137 | Token.Punctuation.CloseBracket,
138 | Token.Punctuation.CloseParen,
139 | Token.Punctuation.OpenBrace,
140 | Token.Comment.LeadingWhitespace(' '),
141 | Token.Comment.SingleLine.Start,
142 | Token.Comment.SingleLine.Text(' break;'),
143 | Token.Punctuation.CloseBrace,
144 | ]);
145 | });
146 |
147 | it('for loop with support types', async () => {
148 | const input = Input.InMethod(`
149 | for (SObject myFancyObject : [SELECT Id, Name FROM Account]){
150 | break;
151 | }`);
152 | const tokens = await tokenize(input);
153 |
154 | tokens.should.deep.equal([
155 | Token.Keywords.Control.For,
156 | Token.Punctuation.OpenParen,
157 | Token.Support.Class.Text('SObject'),
158 | Token.Identifiers.LocalName('myFancyObject'),
159 | Token.Keywords.Control.ColonIterator,
160 | Token.Punctuation.OpenBracket,
161 | Token.Keywords.Queries.Select,
162 | Token.Keywords.Queries.FieldName('Id'),
163 | Token.Punctuation.Comma,
164 | Token.Keywords.Queries.FieldName('Name'),
165 | Token.Keywords.Queries.From,
166 | Token.Keywords.Queries.TypeName('Account'),
167 | Token.Punctuation.CloseBracket,
168 |
169 | Token.Punctuation.CloseParen,
170 | Token.Punctuation.OpenBrace,
171 | Token.Keywords.Control.Break,
172 | Token.Punctuation.Semicolon,
173 | Token.Punctuation.CloseBrace,
174 | ]);
175 | });
176 |
177 | it('for loop of an array or set', async () => {
178 | const input = Input.InMethod(`
179 | for (SObject myFancyObject : SomeArrayOrMap){
180 | break;
181 | }`);
182 | const tokens = await tokenize(input);
183 |
184 | tokens.should.deep.equal([
185 | Token.Keywords.Control.For,
186 | Token.Punctuation.OpenParen,
187 | Token.Support.Class.Text('SObject'),
188 | Token.Identifiers.LocalName('myFancyObject'),
189 | Token.Keywords.Control.ColonIterator,
190 | Token.Variables.ReadWrite('SomeArrayOrMap'),
191 | Token.Punctuation.CloseParen,
192 | Token.Punctuation.OpenBrace,
193 | Token.Keywords.Control.Break,
194 | Token.Punctuation.Semicolon,
195 | Token.Punctuation.CloseBrace,
196 | ]);
197 | });
198 |
199 | it('for loop or an object', async () => {
200 | const input = Input.InMethod(`
201 | for (SObject myFancyObject : myObject.WithMethod()){
202 | break;
203 | }`);
204 | const tokens = await tokenize(input);
205 |
206 | tokens.should.deep.equal([
207 | Token.Keywords.Control.For,
208 | Token.Punctuation.OpenParen,
209 | Token.Support.Class.Text('SObject'),
210 | Token.Identifiers.LocalName('myFancyObject'),
211 | Token.Keywords.Control.ColonIterator,
212 | Token.Variables.Object('myObject'),
213 | Token.Punctuation.Accessor,
214 | Token.Identifiers.MethodName('WithMethod'),
215 | Token.Punctuation.OpenParen,
216 | Token.Punctuation.CloseParen,
217 | Token.Punctuation.CloseParen,
218 | Token.Punctuation.OpenBrace,
219 | Token.Keywords.Control.Break,
220 | Token.Punctuation.Semicolon,
221 | Token.Punctuation.CloseBrace,
222 | ]);
223 | });
224 |
225 | it('for loop or an object with safe navigator', async () => {
226 | const input = Input.InMethod(`
227 | for (SObject myFancyObject : myObject?.WithMethod()){
228 | break;
229 | }`);
230 | const tokens = await tokenize(input);
231 |
232 | tokens.should.deep.equal([
233 | Token.Keywords.Control.For,
234 | Token.Punctuation.OpenParen,
235 | Token.Support.Class.Text('SObject'),
236 | Token.Identifiers.LocalName('myFancyObject'),
237 | Token.Keywords.Control.ColonIterator,
238 | Token.Variables.Object('myObject'),
239 | Token.Operators.SafeNavigation,
240 | Token.Identifiers.MethodName('WithMethod'),
241 | Token.Punctuation.OpenParen,
242 | Token.Punctuation.CloseParen,
243 | Token.Punctuation.CloseParen,
244 | Token.Punctuation.OpenBrace,
245 | Token.Keywords.Control.Break,
246 | Token.Punctuation.Semicolon,
247 | Token.Punctuation.CloseBrace,
248 | ]);
249 | });
250 |
251 | it('for loop a query that uses local variables', async () => {
252 | const input = Input.InMethod(`
253 | for (SObject myFancyObject : [SELECT Id, Name FROM User WHERE Id IN :variable]){
254 | System.debug('This is a test' + myFancyObject);
255 | }`);
256 | const tokens = await tokenize(input);
257 |
258 | tokens.should.deep.equal([
259 | Token.Keywords.Control.For,
260 | Token.Punctuation.OpenParen,
261 | Token.Support.Class.Text('SObject'),
262 | Token.Identifiers.LocalName('myFancyObject'),
263 | Token.Keywords.Control.ColonIterator,
264 | Token.Punctuation.OpenBracket,
265 | Token.Keywords.Queries.Select,
266 | Token.Keywords.Queries.FieldName('Id'),
267 | Token.Punctuation.Comma,
268 | Token.Keywords.Queries.FieldName('Name'),
269 | Token.Keywords.Queries.From,
270 | Token.Keywords.Queries.TypeName('User'),
271 | Token.Keywords.Queries.Where,
272 | Token.Keywords.Queries.FieldName('Id'),
273 | Token.Keywords.Queries.OperatorName('IN'),
274 | Token.Operators.Conditional.Colon,
275 | Token.Identifiers.LocalName('variable'),
276 | Token.Punctuation.CloseBracket,
277 | Token.Punctuation.CloseParen,
278 | Token.Punctuation.OpenBrace,
279 | Token.Support.Class.System,
280 | Token.Punctuation.Accessor,
281 | Token.Support.Class.FunctionText('debug'),
282 | Token.Punctuation.OpenParen,
283 | Token.XmlDocComments.String.SingleQuoted.Begin,
284 | Token.XmlDocComments.String.SingleQuoted.Text('This is a test'),
285 | Token.XmlDocComments.String.SingleQuoted.End,
286 | Token.Operators.Arithmetic.Addition,
287 | Token.Variables.ReadWrite('myFancyObject'),
288 | Token.Punctuation.CloseParen,
289 | Token.Punctuation.Semicolon,
290 | Token.Punctuation.CloseBrace,
291 | ]);
292 | });
293 | });
294 | });
295 |
--------------------------------------------------------------------------------
/test/trigger.test.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) 2018 Salesforce. All rights reserved.
3 | * See LICENSE in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | import { should } from 'chai';
7 | import { tokenize, Input, Token } from './utils/tokenize';
8 |
9 | describe('Grammar', () => {
10 | before(() => {
11 | should();
12 | });
13 |
14 | describe('Apex Trigger', () => {
15 | it('before insert before update Account trigger', async () => {
16 | const input =
17 | Input.FromText(`trigger myAccountTrigger on Account (before insert, after update) {
18 | // Your code here
19 | if(true) {}
20 | }`);
21 | const tokens = await tokenize(input);
22 |
23 | tokens.should.deep.equal([
24 | Token.Keywords.Trigger,
25 | Token.Identifiers.TriggerName('myAccountTrigger'),
26 | Token.Keywords.Triggers.On,
27 | Token.Type('Account'),
28 | Token.Punctuation.OpenParen,
29 | Token.Keywords.Triggers.Before,
30 | Token.Keywords.Triggers.OperatorName('insert'),
31 | Token.Punctuation.Comma,
32 | Token.Keywords.Triggers.After,
33 | Token.Keywords.Triggers.OperatorName('update'),
34 | Token.Punctuation.CloseParen,
35 | Token.Punctuation.OpenBrace,
36 | Token.Comment.LeadingWhitespace(' '),
37 | Token.Comment.SingleLine.Start,
38 | Token.Comment.SingleLine.Text(' Your code here'),
39 | Token.Keywords.Control.If,
40 | Token.Punctuation.OpenParen,
41 | Token.Literals.Boolean.True,
42 | Token.Punctuation.CloseParen,
43 | Token.Punctuation.OpenBrace,
44 | Token.Punctuation.CloseBrace,
45 | Token.Punctuation.CloseBrace,
46 | ]);
47 | });
48 |
49 | it('context variables specific to triggers used in methods', async () => {
50 | const input = Input.InMethod(
51 | `if (Trigger.isBefore && Trigger.isInsert) {}`
52 | );
53 | const tokens = await tokenize(input);
54 |
55 | tokens.should.deep.equal([
56 | Token.Keywords.Control.If,
57 | Token.Punctuation.OpenParen,
58 | Token.Support.Class.Trigger,
59 | Token.Punctuation.Accessor,
60 | Token.Support.Type.TriggerText('isBefore'),
61 | Token.Operators.Logical.And,
62 | Token.Support.Class.Trigger,
63 | Token.Punctuation.Accessor,
64 | Token.Support.Type.TriggerText('isInsert'),
65 | Token.Punctuation.CloseParen,
66 | Token.Punctuation.OpenBrace,
67 | Token.Punctuation.CloseBrace,
68 | ]);
69 | });
70 |
71 | it('context variables specific to triggers', async () => {
72 | const input = Input.InTrigger(
73 | `if (Trigger.isBefore && Trigger.isInsert) {}`
74 | );
75 | const tokens = await tokenize(input);
76 |
77 | tokens.should.deep.equal([
78 | Token.Keywords.Control.If,
79 | Token.Punctuation.OpenParen,
80 | Token.Support.Class.Trigger,
81 | Token.Punctuation.Accessor,
82 | Token.Support.Type.TriggerText('isBefore'),
83 | Token.Operators.Logical.And,
84 | Token.Support.Class.Trigger,
85 | Token.Punctuation.Accessor,
86 | Token.Support.Type.TriggerText('isInsert'),
87 | Token.Punctuation.CloseParen,
88 | Token.Punctuation.OpenBrace,
89 | Token.Punctuation.CloseBrace,
90 | ]);
91 | });
92 |
93 | it('triggers language specific functions', async () => {
94 | const input = Input.InTrigger(`Trigger.newMap.keySet();`);
95 | const tokens = await tokenize(input);
96 |
97 | tokens.should.deep.equal([
98 | Token.Support.Class.Trigger,
99 | Token.Punctuation.Accessor,
100 | Token.Support.Type.TriggerText('newMap'),
101 | Token.Punctuation.Accessor,
102 | Token.Support.Function.TriggerText('keySet'),
103 | Token.Punctuation.OpenParen,
104 | Token.Punctuation.CloseParen,
105 | Token.Punctuation.Semicolon,
106 | ]);
107 | });
108 |
109 | it('triggers language specific functions - complex scenario', async () => {
110 | const input = Input.InTrigger(
111 | `Trigger.newMap.get(q.opportunity__c).addError('Cannot delete quote');`
112 | );
113 | const tokens = await tokenize(input);
114 |
115 | tokens.should.deep.equal([
116 | Token.Support.Class.Trigger,
117 | Token.Punctuation.Accessor,
118 | Token.Support.Type.TriggerText('newMap'),
119 | Token.Punctuation.Accessor,
120 | Token.Support.Function.TriggerText('get'),
121 | Token.Punctuation.OpenParen,
122 | Token.Variables.Object('q'),
123 | Token.Punctuation.Accessor,
124 | Token.Variables.Property('opportunity__c'),
125 | Token.Punctuation.CloseParen,
126 | Token.Punctuation.Accessor,
127 | Token.Support.Function.TriggerText('addError'),
128 | Token.Punctuation.OpenParen,
129 | Token.Punctuation.String.Begin,
130 | Token.XmlDocComments.String.SingleQuoted.Text('Cannot delete quote'),
131 | Token.Punctuation.String.End,
132 | Token.Punctuation.CloseParen,
133 | Token.Punctuation.Semicolon,
134 | ]);
135 | });
136 |
137 | it('triggers language specific functions - complex scenario with safe navigator', async () => {
138 | const input = Input.InTrigger(
139 | `Trigger.newMap.get(q.opportunity__c)?.addError('Cannot delete quote');`
140 | );
141 | const tokens = await tokenize(input);
142 |
143 | tokens.should.deep.equal([
144 | Token.Support.Class.Trigger,
145 | Token.Punctuation.Accessor,
146 | Token.Support.Type.TriggerText('newMap'),
147 | Token.Punctuation.Accessor,
148 | Token.Support.Function.TriggerText('get'),
149 | Token.Punctuation.OpenParen,
150 | Token.Variables.Object('q'),
151 | Token.Punctuation.Accessor,
152 | Token.Variables.Property('opportunity__c'),
153 | Token.Punctuation.CloseParen,
154 | Token.Operators.SafeNavigation,
155 | Token.Support.Function.TriggerText('addError'),
156 | Token.Punctuation.OpenParen,
157 | Token.Punctuation.String.Begin,
158 | Token.XmlDocComments.String.SingleQuoted.Text('Cannot delete quote'),
159 | Token.Punctuation.String.End,
160 | Token.Punctuation.CloseParen,
161 | Token.Punctuation.Semicolon,
162 | ]);
163 | });
164 |
165 | it('SOQL in triggers', async () => {
166 | const input = Input.InTrigger(
167 | `Contact[] cons = [SELECT LastName FROM Contact WHERE AccountId IN :Trigger.new];`
168 | );
169 | const tokens = await tokenize(input);
170 |
171 | tokens.should.deep.equal([
172 | Token.Type('Contact'),
173 | Token.Punctuation.OpenBracket,
174 | Token.Punctuation.CloseBracket,
175 | Token.Identifiers.LocalName('cons'),
176 | Token.Operators.Assignment,
177 | Token.Punctuation.OpenBracket,
178 | Token.Keywords.Queries.Select,
179 | Token.Keywords.Queries.FieldName('LastName'),
180 | Token.Keywords.Queries.From,
181 | Token.Keywords.Queries.TypeName('Contact'),
182 | Token.Keywords.Queries.Where,
183 | Token.Keywords.Queries.FieldName('AccountId'),
184 | Token.Keywords.Queries.OperatorName('IN'),
185 | Token.Operators.Conditional.Colon,
186 | Token.Support.Class.Trigger,
187 | Token.Punctuation.Accessor,
188 | Token.Support.Type.TriggerText('new'),
189 | Token.Punctuation.CloseBracket,
190 | Token.Punctuation.Semicolon,
191 | ]);
192 | });
193 |
194 | it('SOQL in triggers using methods in clauses', async () => {
195 | const input = Input.InTrigger(
196 | `Contact[] cons = [SELECT LastName FROM Contact WHERE AccountId IN :keys('w')];`
197 | );
198 | const tokens = await tokenize(input);
199 |
200 | tokens.should.deep.equal([
201 | Token.Type('Contact'),
202 | Token.Punctuation.OpenBracket,
203 | Token.Punctuation.CloseBracket,
204 | Token.Identifiers.LocalName('cons'),
205 | Token.Operators.Assignment,
206 | Token.Punctuation.OpenBracket,
207 | Token.Keywords.Queries.Select,
208 | Token.Keywords.Queries.FieldName('LastName'),
209 | Token.Keywords.Queries.From,
210 | Token.Keywords.Queries.TypeName('Contact'),
211 | Token.Keywords.Queries.Where,
212 | Token.Keywords.Queries.FieldName('AccountId'),
213 | Token.Keywords.Queries.OperatorName('IN'),
214 | Token.Operators.Conditional.Colon,
215 | Token.Identifiers.MethodName('keys'),
216 | Token.Punctuation.OpenParen,
217 | Token.Punctuation.String.Begin,
218 | Token.XmlDocComments.String.SingleQuoted.Text('w'),
219 | Token.Punctuation.String.End,
220 | Token.Punctuation.CloseParen,
221 | Token.Punctuation.CloseBracket,
222 | Token.Punctuation.Semicolon,
223 | ]);
224 | });
225 |
226 | it('SOQL in triggers using objects in clauses', async () => {
227 | const input = Input.InTrigger(
228 | `Contact[] cons = [SELECT LastName FROM Contact WHERE AccountId IN :myObject.keys('w')];`
229 | );
230 | const tokens = await tokenize(input);
231 |
232 | tokens.should.deep.equal([
233 | Token.Type('Contact'),
234 | Token.Punctuation.OpenBracket,
235 | Token.Punctuation.CloseBracket,
236 | Token.Identifiers.LocalName('cons'),
237 | Token.Operators.Assignment,
238 | Token.Punctuation.OpenBracket,
239 | Token.Keywords.Queries.Select,
240 | Token.Keywords.Queries.FieldName('LastName'),
241 | Token.Keywords.Queries.From,
242 | Token.Keywords.Queries.TypeName('Contact'),
243 | Token.Keywords.Queries.Where,
244 | Token.Keywords.Queries.FieldName('AccountId'),
245 | Token.Keywords.Queries.OperatorName('IN'),
246 | Token.Operators.Conditional.Colon,
247 | Token.Variables.Object('myObject'),
248 | Token.Punctuation.Accessor,
249 | Token.Identifiers.MethodName('keys'),
250 | Token.Punctuation.OpenParen,
251 | Token.Punctuation.String.Begin,
252 | Token.XmlDocComments.String.SingleQuoted.Text('w'),
253 | Token.Punctuation.String.End,
254 | Token.Punctuation.CloseParen,
255 | Token.Punctuation.CloseBracket,
256 | Token.Punctuation.Semicolon,
257 | ]);
258 | });
259 |
260 | it('SOQL in triggers using objects in clauses with safe navigator', async () => {
261 | const input = Input.InTrigger(
262 | `Contact[] cons = [SELECT LastName FROM Contact WHERE AccountId IN :myObject?.keys('w')];`
263 | );
264 | const tokens = await tokenize(input);
265 |
266 | tokens.should.deep.equal([
267 | Token.Type('Contact'),
268 | Token.Punctuation.OpenBracket,
269 | Token.Punctuation.CloseBracket,
270 | Token.Identifiers.LocalName('cons'),
271 | Token.Operators.Assignment,
272 | Token.Punctuation.OpenBracket,
273 | Token.Keywords.Queries.Select,
274 | Token.Keywords.Queries.FieldName('LastName'),
275 | Token.Keywords.Queries.From,
276 | Token.Keywords.Queries.TypeName('Contact'),
277 | Token.Keywords.Queries.Where,
278 | Token.Keywords.Queries.FieldName('AccountId'),
279 | Token.Keywords.Queries.OperatorName('IN'),
280 | Token.Operators.Conditional.Colon,
281 | Token.Variables.Object('myObject'),
282 | Token.Operators.SafeNavigation,
283 | Token.Identifiers.MethodName('keys'),
284 | Token.Punctuation.OpenParen,
285 | Token.Punctuation.String.Begin,
286 | Token.XmlDocComments.String.SingleQuoted.Text('w'),
287 | Token.Punctuation.String.End,
288 | Token.Punctuation.CloseParen,
289 | Token.Punctuation.CloseBracket,
290 | Token.Punctuation.Semicolon,
291 | ]);
292 | });
293 | });
294 | });
295 |
--------------------------------------------------------------------------------