-
Notifications
You must be signed in to change notification settings - Fork 745
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This commit adds a lexer for the Apex language.
- Loading branch information
Showing
6 changed files
with
336 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
public class with sharing Trigger { | ||
@Deprecated | ||
public void resolveSum(int x, int y) { | ||
System.debug('x is ' + x); | ||
System.debug('y is ' + y); | ||
|
||
System.debug('x + y = ' + (x+y)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
# -*- coding: utf-8 -*- # | ||
# frozen_string_literal: true | ||
|
||
module Rouge | ||
module Lexers | ||
class Apex < RegexLexer | ||
title "Apex" | ||
desc "The Apex programming language (provided by salesforce)" | ||
|
||
tag 'apex' | ||
filenames '*.cls' | ||
mimetypes 'text/x-apex' | ||
|
||
def self.keywords | ||
@keywords ||= Set.new %w( | ||
assert break case catch continue default do else finally for if goto | ||
instanceof new return switch this throw try while insert update | ||
delete | ||
) | ||
end | ||
|
||
def self.declarations | ||
@declarations ||= Set.new %w( | ||
abstract const enum extends final implements native private protected | ||
public static super synchronized throws transient volatile with | ||
sharing without inherited virtual global testmethod | ||
) | ||
end | ||
|
||
def self.soql | ||
@soql ||= Set.new %w( | ||
SELECT FROM WHERE UPDATE LIKE TYPEOF END USING SCOPE WITH DATA | ||
CATEGORY GROUP BY ROLLUP CUBE HAVING ORDER BY ASC DESC NULLS FIRST | ||
LAST LIMIT OFFSET FOR VIEW REFERENCE UPDATE TRACKING VIEWSTAT OR AND | ||
) | ||
end | ||
|
||
def self.types | ||
@types ||= Set.new %w( | ||
String boolean byte char double float int long short var void | ||
) | ||
end | ||
|
||
def self.constants | ||
@constants ||= Set.new %w(true false null) | ||
end | ||
|
||
id = /[a-z_][a-z0-9_]*/i | ||
|
||
state :root do | ||
rule %r/\s+/m, Text | ||
|
||
rule %r(//.*?$), Comment::Single | ||
rule %r(/\*.*?\*/)m, Comment::Multiline | ||
|
||
rule %r/(?:class|interface)\b/, Keyword::Declaration, :class | ||
rule %r/import\b/, Keyword::Namespace, :import | ||
|
||
rule %r/([@$.]?)(#{id})([:(]?)/io do |m| | ||
if self.class.keywords.include? m[0].downcase | ||
token Keyword | ||
elsif self.class.soql.include? m[0].upcase | ||
token Keyword | ||
elsif self.class.declarations.include? m[0].downcase | ||
token Keyword::Declaration | ||
elsif self.class.types.include? m[0].downcase | ||
token Keyword::Type | ||
elsif self.class.constants.include? m[0].downcase | ||
token Keyword::Constant | ||
elsif 'package'.casecmp m[0] | ||
token Keyword::Namespace | ||
elsif m[1] == "@" | ||
token Name::Decorator | ||
elsif m[3] == ":" | ||
groups Operator, Name::Label, Punctuation | ||
elsif m[3] == "(" | ||
groups Operator, Name::Function, Punctuation | ||
elsif m[1] == "." | ||
groups Operator, Name::Property, Punctuation | ||
else | ||
token Name | ||
end | ||
end | ||
|
||
rule %r/"/, Str::Double, :dq | ||
rule %r/'/, Str::Single, :sq | ||
|
||
digit = /[0-9]_+[0-9]|[0-9]/ | ||
rule %r/#{digit}+\.#{digit}+([eE]#{digit}+)?[fd]?/, Num::Float | ||
rule %r/0b(?:[01]_+[01]|[01])+/i, Num::Bin | ||
rule %r/0x(?:\h_+\h|\h)+/i, Num::Hex | ||
rule %r/0(?:[0-7]_+[0-7]|[0-7])+/, Num::Oct | ||
rule %r/#{digit}+L?/, Num::Integer | ||
|
||
rule %r/[-+\/*~^!%&<>|=.?]/, Operator | ||
rule %r/[\[\](){},:;]/, Punctuation; | ||
end | ||
|
||
state :class do | ||
rule %r/\s+/m, Text | ||
rule id, Name::Class, :pop! | ||
end | ||
|
||
state :import do | ||
rule %r/\s+/m, Text | ||
rule %r/[a-z0-9_.]+\*?/i, Name::Namespace, :pop! | ||
end | ||
|
||
state :escape do | ||
rule %r/\\[btnfr\\"']/, Str::Escape | ||
end | ||
|
||
state :dq do | ||
mixin :escape | ||
rule %r/[^\\"]+/, Str::Double | ||
rule %r/"/, Str::Double, :pop! | ||
end | ||
|
||
state :sq do | ||
mixin :escape | ||
rule %r/[^\\']+/, Str::Double | ||
rule %r/'/, Str::Double, :pop! | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# -*- coding: utf-8 -*- # | ||
# frozen_string_literal: true | ||
|
||
describe Rouge::Lexers::Apex do | ||
let(:subject) { Rouge::Lexers::Apex.new } | ||
|
||
describe 'guessing' do | ||
include Support::Guessing | ||
|
||
it 'guesses by filename' do | ||
assert_guess :filename => 'foo.cls', :source => '// A comment' | ||
end | ||
|
||
it 'guesses by mimetype' do | ||
assert_guess :mimetype => 'text/x-apex' | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
////// badcase.apex ////// | ||
|
||
if (Reports.get(0) instanceof CustomReport) { | ||
// Can safely cast it back to a custom report object | ||
CustomReport c = (CustomReport) Reports.get(0); | ||
} else { | ||
// Do something with the non-custom-report. | ||
} | ||
|
||
Object o = null; | ||
Boolean result = o instanceof Account; | ||
System.assertEquals(false, result); | ||
|
||
String dq = "This is a double-quoted string."; | ||
String sq = 'This is a single-quoted string.'; | ||
String es_dq = "This is a string with \" escape sequences \b."; | ||
String es_sq = 'This is a string with \' escape sequences \n.'; | ||
|
||
1; | ||
1.0; | ||
0b01; | ||
0xFFFFFF; | ||
01; | ||
|
||
public virtual class SuperClass { | ||
public String mySalutation; | ||
public String myFirstName; | ||
public String myLastName; | ||
|
||
public SuperClass() { | ||
|
||
mySalutation = 'Mr.'; | ||
myFirstName = 'Carl'; | ||
myLastName = 'Vonderburg'; | ||
} | ||
|
||
public SuperClass(String salutation, String firstName, String lastName) { | ||
|
||
mySalutation = salutation; | ||
myFirstName = firstName; | ||
myLastName = lastName; | ||
} | ||
|
||
public virtual void printName() { | ||
|
||
System.debug('My name is ' + mySalutation + myLastName); | ||
} | ||
|
||
public virtual String getFirstName() { | ||
return myFirstName; | ||
} | ||
} | ||
|
||
public class Subclass extends Superclass { | ||
public override void printName() { | ||
super.printName(); | ||
System.debug('But you can call me ' + super.getFirstName()); | ||
} | ||
} | ||
|
||
public Subclass() { | ||
super('Madam', 'Brenda', 'Clapentrap'); | ||
} | ||
|
||
// using `this` | ||
public class myTestThis { | ||
|
||
string s; | ||
{ | ||
this.s = 'TestString'; | ||
} | ||
} | ||
|
||
public class testThis { | ||
|
||
// First constructor for the class. It requires a string parameter. | ||
public testThis(string s2) { | ||
} | ||
|
||
// Second constructor for the class. It does not require a parameter. | ||
// This constructor calls the first constructor using the this keyword. | ||
public testThis() { | ||
this('None'); | ||
} | ||
} | ||
|
||
// using transient | ||
public class ExampleController { | ||
|
||
DateTime t1; | ||
transient DateTime t2; | ||
|
||
public String getT1() { | ||
if (t1 == null) t1 = System.now(); | ||
return '' + t1; | ||
} | ||
|
||
public String getT2() { | ||
if (t2 == null) t2 = System.now(); | ||
return '' + t2; | ||
} | ||
} | ||
|
||
// page controller | ||
public inherited sharing class InheritedSharingClass{ | ||
public List<Contact> getAllTheSecrets(){ | ||
return [SELECT Name FROM Contact]; | ||
} | ||
} | ||
|
||
// annotations | ||
global class MyClass { | ||
|
||
@Future(callout=true) @IsTest | ||
public static void myMethod(String a) | ||
{ | ||
/* ... */ | ||
} | ||
} | ||
|
||
// Invokables | ||
public without sharing class AccountQueryAction { | ||
@InvocableMethod(label='Get Account Names' description='Returns the list of account names corresponding to the specified account IDs.') | ||
public static List<String> getAccountNames(List<ID> ids) { | ||
List<String> accountNames = new List<String>(); | ||
List<Account> accounts = [SELECT Name FROM Account WHERE Id in :ids]; | ||
for (Account account : accounts) { | ||
accountNames.add(account.Name); | ||
} | ||
return accountNames; | ||
} | ||
} | ||
|
||
public class AccountInsertAction { | ||
@InvocableMethod(label='Insert Accounts' description='Inserts the accounts specified and returns the IDs of the new accounts.') | ||
public static List<ID> insertAccounts(List<Account> accounts) { | ||
Database.SaveResult[] results = Database.insert(accounts); | ||
List<ID> accountIds = new List<ID>(); | ||
for (Database.SaveResult result : results) { | ||
if (result.isSuccess()) { | ||
accountIds.add(result.getId()); | ||
} | ||
} | ||
return accountIds; | ||
} | ||
} | ||
|
||
// test class | ||
@isTest(SeeAllData=true) | ||
public class TestDataAccessClass { | ||
|
||
static testmethod void myTestMethod1() { | ||
// Query an existing account in the organization. | ||
Account a = [SELECT Id, Name FROM Account WHERE Name='Acme' LIMIT 1]; | ||
System.assert(a != null); | ||
|
||
// Create a test account based on the queried account. | ||
Account testAccount = a.clone(); | ||
testAccount.Name = 'Acme Test'; | ||
|
||
insert testAccount; | ||
|
||
// Query the test account that was inserted. | ||
Account testAccount2 = [SELECT Id, Name FROM Account | ||
WHERE Name='Acme Test' LIMIT 1]; | ||
|
||
System.assert(testAccount2 != null); | ||
} | ||
|
||
@isTest static void myTestMethod2() { | ||
// Can access all data in the organization. | ||
} | ||
|
||
} | ||
|
||
// Soql | ||
[SELECT Company, toLabel(Status) FROM Lead WHERE toLabel(Status) = 'le Draft'] |