(?=\\s*\\w+\\s*[;=,(){:])"),inside:a}],trigger:{pattern:/(\btrigger\s+)\w+\b/i,lookbehind:!0,alias:"class-name"},keyword:t,function:/\b[a-z_]\w*(?=\s*\()/i,boolean:/\b(?:false|true)\b/i,number:/(?:\B\.\d+|\b\d+(?:\.\d+|L)?)\b/i,operator:/[!=](?:==?)?|\?\.?|&&|\|\||--|\+\+|[-+*/^&|]=?|:|<=?|>{1,3}=?/,punctuation:/[()\[\]{};,.]/}}(Prism);
\ No newline at end of file
diff --git a/examples/prism-apex.html b/examples/prism-apex.html
new file mode 100644
index 0000000000..f942f29da3
--- /dev/null
+++ b/examples/prism-apex.html
@@ -0,0 +1,152 @@
+Full example
+// source: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_shopping_cart_example_code.htm
+
+trigger calculate on Item__c (after insert, after update, after delete) {
+
+// Use a map because it doesn't allow duplicate values
+
+Map<ID, Shipping_Invoice__C> updateMap = new Map<ID, Shipping_Invoice__C>();
+
+// Set this integer to -1 if we are deleting
+Integer subtract ;
+
+// Populate the list of items based on trigger type
+List<Item__c> itemList;
+ if(trigger.isInsert || trigger.isUpdate){
+ itemList = Trigger.new;
+ subtract = 1;
+ }
+ else if(trigger.isDelete)
+ {
+ // Note -- there is no trigger.new in delete
+ itemList = trigger.old;
+ subtract = -1;
+ }
+
+// Access all the information we need in a single query
+// rather than querying when we need it.
+// This is a best practice for bulkifying requests
+
+set<Id> AllItems = new set<id>();
+
+for(item__c i :itemList){
+// Assert numbers are not negative.
+// None of the fields would make sense with a negative value
+
+System.assert(i.quantity__c > 0, 'Quantity must be positive');
+System.assert(i.weight__c >= 0, 'Weight must be non-negative');
+System.assert(i.price__c >= 0, 'Price must be non-negative');
+
+// If there is a duplicate Id, it won't get added to a set
+AllItems.add(i.Shipping_Invoice__C);
+}
+
+// Accessing all shipping invoices associated with the items in the trigger
+List<Shipping_Invoice__C> AllShippingInvoices = [SELECT Id, ShippingDiscount__c,
+ SubTotal__c, TotalWeight__c, Tax__c, GrandTotal__c
+ FROM Shipping_Invoice__C WHERE Id IN :AllItems];
+
+// Take the list we just populated and put it into a Map.
+// This will make it easier to look up a shipping invoice
+// because you must iterate a list, but you can use lookup for a map,
+Map<ID, Shipping_Invoice__C> SIMap = new Map<ID, Shipping_Invoice__C>();
+
+for(Shipping_Invoice__C sc : AllShippingInvoices)
+{
+ SIMap.put(sc.id, sc);
+}
+
+// Process the list of items
+ if(Trigger.isUpdate)
+ {
+ // Treat updates like a removal of the old item and addition of the
+ // revised item rather than figuring out the differences of each field
+ // and acting accordingly.
+ // Note updates have both trigger.new and trigger.old
+ for(Integer x = 0; x < Trigger.old.size(); x++)
+ {
+ Shipping_Invoice__C myOrder;
+ myOrder = SIMap.get(trigger.old[x].Shipping_Invoice__C);
+
+ // Decrement the previous value from the subtotal and weight.
+ myOrder.SubTotal__c -= (trigger.old[x].price__c *
+ trigger.old[x].quantity__c);
+ myOrder.TotalWeight__c -= (trigger.old[x].weight__c *
+ trigger.old[x].quantity__c);
+
+ // Increment the new subtotal and weight.
+ myOrder.SubTotal__c += (trigger.new[x].price__c *
+ trigger.new[x].quantity__c);
+ myOrder.TotalWeight__c += (trigger.new[x].weight__c *
+ trigger.new[x].quantity__c);
+ }
+
+ for(Shipping_Invoice__C myOrder : AllShippingInvoices)
+ {
+
+ // Set tax rate to 9.25% Please note, this is a simple example.
+ // Generally, you would never hard code values.
+ // Leveraging Custom Settings for tax rates is a best practice.
+ // See Custom Settings in the Apex Developer Guide
+ // for more information.
+ myOrder.Tax__c = myOrder.Subtotal__c * .0925;
+
+ // Reset the shipping discount
+ myOrder.ShippingDiscount__c = 0;
+
+ // Set shipping rate to 75 cents per pound.
+ // Generally, you would never hard code values.
+ // Leveraging Custom Settings for the shipping rate is a best practice.
+ // See Custom Settings in the Apex Developer Guide
+ // for more information.
+ myOrder.Shipping__c = (myOrder.totalWeight__c * .75);
+ myOrder.GrandTotal__c = myOrder.SubTotal__c + myOrder.tax__c +
+ myOrder.Shipping__c;
+ updateMap.put(myOrder.id, myOrder);
+ }
+ }
+ else
+ {
+ for(Item__c itemToProcess : itemList)
+ {
+ Shipping_Invoice__C myOrder;
+
+ // Look up the correct shipping invoice from the ones we got earlier
+ myOrder = SIMap.get(itemToProcess.Shipping_Invoice__C);
+ myOrder.SubTotal__c += (itemToProcess.price__c *
+ itemToProcess.quantity__c * subtract);
+ myOrder.TotalWeight__c += (itemToProcess.weight__c *
+ itemToProcess.quantity__c * subtract);
+ }
+
+ for(Shipping_Invoice__C myOrder : AllShippingInvoices)
+ {
+
+ // Set tax rate to 9.25% Please note, this is a simple example.
+ // Generally, you would never hard code values.
+ // Leveraging Custom Settings for tax rates is a best practice.
+ // See Custom Settings in the Apex Developer Guide
+ // for more information.
+ myOrder.Tax__c = myOrder.Subtotal__c * .0925;
+
+ // Reset shipping discount
+ myOrder.ShippingDiscount__c = 0;
+
+ // Set shipping rate to 75 cents per pound.
+ // Generally, you would never hard code values.
+ // Leveraging Custom Settings for the shipping rate is a best practice.
+ // See Custom Settings in the Apex Developer Guide
+ // for more information.
+ myOrder.Shipping__c = (myOrder.totalWeight__c * .75);
+ myOrder.GrandTotal__c = myOrder.SubTotal__c + myOrder.tax__c +
+ myOrder.Shipping__c;
+
+ updateMap.put(myOrder.id, myOrder);
+
+ }
+ }
+
+ // Only use one DML update at the end.
+ // This minimizes the number of DML requests generated from this trigger.
+ update updateMap.values();
+}
diff --git a/plugins/autoloader/prism-autoloader.js b/plugins/autoloader/prism-autoloader.js
index 498e3cffe0..48b90c390f 100644
--- a/plugins/autoloader/prism-autoloader.js
+++ b/plugins/autoloader/prism-autoloader.js
@@ -11,6 +11,10 @@
var lang_dependencies = /*dependencies_placeholder[*/{
"javascript": "clike",
"actionscript": "javascript",
+ "apex": [
+ "clike",
+ "sql"
+ ],
"arduino": "cpp",
"aspnet": [
"markup",
diff --git a/plugins/autoloader/prism-autoloader.min.js b/plugins/autoloader/prism-autoloader.min.js
index 695cf2aec2..dd5b4113ce 100644
--- a/plugins/autoloader/prism-autoloader.min.js
+++ b/plugins/autoloader/prism-autoloader.min.js
@@ -1 +1 @@
-!function(){if("undefined"!=typeof self&&self.Prism&&self.document&&document.createElement){var l={javascript:"clike",actionscript:"javascript",arduino:"cpp",aspnet:["markup","csharp"],birb:"clike",bison:"c",c:"clike",csharp:"clike",cpp:"c",coffeescript:"javascript",crystal:"ruby","css-extras":"css",d:"clike",dart:"clike",django:"markup-templating",ejs:["javascript","markup-templating"],etlua:["lua","markup-templating"],erb:["ruby","markup-templating"],fsharp:"clike","firestore-security-rules":"clike",flow:"javascript",ftl:"markup-templating",gml:"clike",glsl:"c",go:"clike",groovy:"clike",haml:"ruby",handlebars:"markup-templating",haxe:"clike",hlsl:"c",java:"clike",javadoc:["markup","java","javadoclike"],jolie:"clike",jsdoc:["javascript","javadoclike","typescript"],"js-extras":"javascript",json5:"json",jsonp:"json","js-templates":"javascript",kotlin:"clike",latte:["clike","markup-templating","php"],less:"css",lilypond:"scheme",markdown:"markup","markup-templating":"markup",mongodb:"javascript",n4js:"javascript",nginx:"clike",objectivec:"c",opencl:"c",parser:"markup",php:"markup-templating",phpdoc:["php","javadoclike"],"php-extras":"php",plsql:"sql",processing:"clike",protobuf:"clike",pug:["markup","javascript"],purebasic:"clike",purescript:"haskell",qml:"javascript",qore:"clike",racket:"scheme",jsx:["markup","javascript"],tsx:["jsx","typescript"],reason:"clike",ruby:"clike",sass:"css",scss:"css",scala:"java","shell-session":"bash",smarty:"markup-templating",solidity:"clike",soy:"markup-templating",sparql:"turtle",sqf:"clike",swift:"clike","t4-cs":["t4-templating","csharp"],"t4-vb":["t4-templating","vbnet"],tap:"yaml",tt2:["clike","markup-templating"],textile:"markup",twig:"markup",typescript:"javascript",vala:"clike",vbnet:"basic",velocity:"markup",wiki:"markup",xeora:"markup","xml-doc":"markup",xquery:"markup"},n={html:"markup",xml:"markup",svg:"markup",mathml:"markup",ssml:"markup",atom:"markup",rss:"markup",js:"javascript",g4:"antlr4",adoc:"asciidoc",shell:"bash",shortcode:"bbcode",rbnf:"bnf",oscript:"bsl",cs:"csharp",dotnet:"csharp",coffee:"coffeescript",conc:"concurnas",jinja2:"django","dns-zone":"dns-zone-file",dockerfile:"docker",eta:"ejs",xlsx:"excel-formula",xls:"excel-formula",gamemakerlanguage:"gml",hs:"haskell",gitignore:"ignore",hgignore:"ignore",npmignore:"ignore",webmanifest:"json",kt:"kotlin",kts:"kotlin",tex:"latex",context:"latex",ly:"lilypond",emacs:"lisp",elisp:"lisp","emacs-lisp":"lisp",md:"markdown",moon:"moonscript",n4jsd:"n4js",nani:"naniscript",objc:"objectivec",objectpascal:"pascal",px:"pcaxis",pcode:"peoplecode",pq:"powerquery",mscript:"powerquery",pbfasm:"purebasic",purs:"purescript",py:"python",rkt:"racket",rpy:"renpy",robot:"robotframework",rb:"ruby","sh-session":"shell-session",shellsession:"shell-session",smlnj:"sml",sol:"solidity",sln:"solution-file",rq:"sparql",t4:"t4-cs",trig:"turtle",ts:"typescript",tsconfig:"typoscript",uscript:"unrealscript",uc:"unrealscript",vb:"visual-basic",vba:"visual-basic",xeoracube:"xeora",yml:"yaml"},p={},e="components/",a=Prism.util.currentScript();if(a){var r=/\bplugins\/autoloader\/prism-autoloader\.(?:min\.)?js(?:\?[^\r\n/]*)?$/i,s=/(^|\/)[\w-]+\.(?:min\.)?js(?:\?[^\r\n/]*)?$/i,t=a.getAttribute("data-autoloader-path");if(null!=t)e=t.trim().replace(/\/?$/,"/");else{var i=a.src;r.test(i)?e=i.replace(r,"components/"):s.test(i)&&(e=i.replace(s,"$1components/"))}}var o=Prism.plugins.autoloader={languages_path:e,use_minified:!0,loadLanguages:m};Prism.hooks.add("complete",function(e){var a=e.element,r=e.language;if(a&&r&&"none"!==r){var s=function(e){var a=(e.getAttribute("data-dependencies")||"").trim();if(!a){var r=e.parentElement;r&&"pre"===r.tagName.toLowerCase()&&(a=(r.getAttribute("data-dependencies")||"").trim())}return a?a.split(/\s*,\s*/g):[]}(a);/^diff-./i.test(r)?(s.push("diff"),s.push(r.substr("diff-".length))):s.push(r),s.every(u)||m(s,function(){Prism.highlightElement(a)})}})}function u(e){if(0<=e.indexOf("!"))return!1;if((e=n[e]||e)in Prism.languages)return!0;var a=p[e];return a&&!a.error&&!1===a.loading}function m(e,a,r){"string"==typeof e&&(e=[e]);var s=e.length,t=0,i=!1;function c(){i||++t===s&&a&&a(e)}0!==s?e.forEach(function(e){!function(a,r,s){var t=0<=a.indexOf("!");function e(){var e=p[a];e||(e=p[a]={callbacks:[]}),e.callbacks.push({success:r,error:s}),!t&&u(a)?k(a,"success"):!t&&e.error?k(a,"error"):!t&&e.loading||(e.loading=!0,e.error=!1,function(e,a,r){var s=document.createElement("script");s.src=e,s.async=!0,s.onload=function(){document.body.removeChild(s),a&&a()},s.onerror=function(){document.body.removeChild(s),r&&r()},document.body.appendChild(s)}(function(e){return o.languages_path+"prism-"+e+(o.use_minified?".min":"")+".js"}(a),function(){e.loading=!1,k(a,"success")},function(){e.loading=!1,e.error=!0,k(a,"error")}))}a=a.replace("!",""),a=n[a]||a;var i=l[a];i&&i.length?m(i,e,s):e()}(e,c,function(){i||(i=!0,r&&r(e))})}):a&&setTimeout(a,0)}function k(e,a){if(p[e]){for(var r=p[e].callbacks,s=0,t=r.length;s itemList;
+List>> my_list_2 = new List>>();
+Map updateMap = new Map();
+set AllItems = new set();
+
+public Season getSouthernHemisphereSeason(season northernHemisphereSeason) {}
+
+for(Shipping_Invoice__C sc : AllShippingInvoices){}
+public static Integer calculate() {}
+
+if (sobject instanceof Account) {
+ Account a = (Account) sobject;
+}
+
+public class myOuterClass {}
+public enum MyEnumClass { X, Y }
+public class YellowMarker extends Marker {}
+interface MySecondInterface extends MyInterface {}
+public virtual class InnerClass implements MySecondInterface {}
+
+
+class Foo {
+ List accs {get; set;}
+ Integer i {get; set;}
+}
+
+
+public with sharing class sharingClass {}
+public without sharing class noSharing {}
+public inherited sharing class InheritedSharingClass{}
+
+----------------------------------------------------
+
+[
+ ["keyword", "Integer"],
+ " i",
+ ["punctuation", ";"],
+ ["keyword", "Integer"],
+ " i ",
+ ["operator", "="],
+ ["punctuation", "("],
+ ["keyword", "Integer"],
+ ["punctuation", ")"],
+ "obj",
+ ["punctuation", ";"],
+ ["class-name", [
+ ["keyword", "Integer"],
+ ["punctuation", "["],
+ ["punctuation", "]"]
+ ]],
+ " myInts ",
+ ["operator", "="],
+ ["keyword", "new"],
+ ["class-name", [
+ ["keyword", "Integer"],
+ ["punctuation", "["],
+ ["punctuation", "]"]
+ ]],
+ ["punctuation", "{"],
+ ["number", "1"],
+ ["punctuation", ","],
+ ["number", "2"],
+ ["punctuation", ","],
+ ["number", "3"],
+ ["punctuation", ","],
+ ["number", "4"],
+ ["punctuation", ","],
+ ["number", "5"],
+ ["punctuation", ","],
+ ["number", "6"],
+ ["punctuation", ","],
+ ["number", "7"],
+ ["punctuation", ","],
+ ["number", "8"],
+ ["punctuation", ","],
+ ["number", "9"],
+ ["punctuation", ","],
+ ["number", "10"],
+ ["punctuation", "}"],
+ ["punctuation", ";"],
+ ["keyword", "Object"],
+ " obj ",
+ ["operator", "="],
+ ["keyword", "new"],
+ ["class-name", [
+ "MyApexClass"
+ ]],
+ ["punctuation", "("],
+ ["punctuation", ")"],
+ ["punctuation", ";"],
+ ["class-name", [
+ ["keyword", "list"],
+ ["punctuation", "<"],
+ "Item__c",
+ ["punctuation", ">"]
+ ]],
+ " itemList",
+ ["punctuation", ";"],
+ ["class-name", [
+ ["keyword", "List"],
+ ["punctuation", "<"],
+ ["keyword", "List"],
+ ["punctuation", "<"],
+ ["keyword", "Set"],
+ ["punctuation", "<"],
+ ["keyword", "Integer"],
+ ["punctuation", ">"],
+ ["punctuation", ">"],
+ ["punctuation", ">"]
+ ]],
+ " my_list_2 ",
+ ["operator", "="],
+ ["keyword", "new"],
+ ["class-name", [
+ ["keyword", "List"],
+ ["punctuation", "<"],
+ ["keyword", "List"],
+ ["punctuation", "<"],
+ ["keyword", "Set"],
+ ["punctuation", "<"],
+ ["keyword", "Integer"],
+ ["punctuation", ">"],
+ ["punctuation", ">"],
+ ["punctuation", ">"]
+ ]],
+ ["punctuation", "("],
+ ["punctuation", ")"],
+ ["punctuation", ";"],
+ ["class-name", [
+ ["keyword", "Map"],
+ ["punctuation", "<"],
+ "ID",
+ ["punctuation", ","],
+ " Shipping_Invoice__C",
+ ["punctuation", ">"]
+ ]],
+ " updateMap ",
+ ["operator", "="],
+ ["keyword", "new"],
+ ["class-name", [
+ ["keyword", "Map"],
+ ["punctuation", "<"],
+ "ID",
+ ["punctuation", ","],
+ " Shipping_Invoice__C",
+ ["punctuation", ">"]
+ ]],
+ ["punctuation", "("],
+ ["punctuation", ")"],
+ ["punctuation", ";"],
+ ["class-name", [
+ ["keyword", "set"],
+ ["punctuation", "<"],
+ "Id",
+ ["punctuation", ">"]
+ ]],
+ " AllItems ",
+ ["operator", "="],
+ ["keyword", "new"],
+ ["class-name", [
+ ["keyword", "set"],
+ ["punctuation", "<"],
+ "id",
+ ["punctuation", ">"]
+ ]],
+ ["punctuation", "("],
+ ["punctuation", ")"],
+ ["punctuation", ";"],
+
+ ["keyword", "public"],
+ ["class-name", [
+ "Season"
+ ]],
+ ["function", "getSouthernHemisphereSeason"],
+ ["punctuation", "("],
+ ["class-name", [
+ "season"
+ ]],
+ " northernHemisphereSeason",
+ ["punctuation", ")"],
+ ["punctuation", "{"],
+ ["punctuation", "}"],
+
+ ["keyword", "for"],
+ ["punctuation", "("],
+ ["class-name", [
+ "Shipping_Invoice__C"
+ ]],
+ " sc ",
+ ["operator", ":"],
+ " AllShippingInvoices",
+ ["punctuation", ")"],
+ ["punctuation", "{"],
+ ["punctuation", "}"],
+ ["keyword", "public"],
+ ["keyword", "static"],
+ ["keyword", "Integer"],
+ ["function", "calculate"],
+ ["punctuation", "("],
+ ["punctuation", ")"],
+ ["punctuation", "{"],
+ ["punctuation", "}"],
+
+ ["keyword", "if"],
+ ["punctuation", "("],
+ ["keyword", "sobject"],
+ ["keyword", "instanceof"],
+ ["class-name", [
+ "Account"
+ ]],
+ ["punctuation", ")"],
+ ["punctuation", "{"],
+ ["class-name", [
+ "Account"
+ ]],
+ " a ",
+ ["operator", "="],
+ ["punctuation", "("],
+ ["class-name", [
+ "Account"
+ ]],
+ ["punctuation", ")"],
+ ["keyword", "sobject"],
+ ["punctuation", ";"],
+ ["punctuation", "}"],
+
+ ["keyword", "public"],
+ ["keyword", "class"],
+ ["class-name", [
+ "myOuterClass"
+ ]],
+ ["punctuation", "{"],
+ ["punctuation", "}"],
+ ["keyword", "public"],
+ ["keyword", "enum"],
+ ["class-name", [
+ "MyEnumClass"
+ ]],
+ ["punctuation", "{"],
+ " X",
+ ["punctuation", ","],
+ " Y ",
+ ["punctuation", "}"],
+ ["keyword", "public"],
+ ["keyword", "class"],
+ ["class-name", [
+ "YellowMarker"
+ ]],
+ ["keyword", "extends"],
+ ["class-name", [
+ "Marker"
+ ]],
+ ["punctuation", "{"],
+ ["punctuation", "}"],
+ ["keyword", "interface"],
+ ["class-name", [
+ "MySecondInterface"
+ ]],
+ ["keyword", "extends"],
+ ["class-name", [
+ "MyInterface"
+ ]],
+ ["punctuation", "{"],
+ ["punctuation", "}"],
+ ["keyword", "public"],
+ ["keyword", "virtual"],
+ ["keyword", "class"],
+ ["class-name", [
+ "InnerClass"
+ ]],
+ ["keyword", "implements"],
+ ["class-name", [
+ "MySecondInterface"
+ ]],
+ ["punctuation", "{"],
+ ["punctuation", "}"],
+
+ ["keyword", "class"],
+ ["class-name", [
+ "Foo"
+ ]],
+ ["punctuation", "{"],
+ ["class-name", [
+ ["keyword", "List"],
+ ["punctuation", "<"],
+ "Account",
+ ["punctuation", ">"]
+ ]],
+ " accs ",
+ ["punctuation", "{"],
+ ["keyword", "get"],
+ ["punctuation", ";"],
+ ["keyword", "set"],
+ ["punctuation", ";"],
+ ["punctuation", "}"],
+ ["keyword", "Integer"],
+ " i ",
+ ["punctuation", "{"],
+ ["keyword", "get"],
+ ["punctuation", ";"],
+ ["keyword", "set"],
+ ["punctuation", ";"],
+ ["punctuation", "}"],
+ ["punctuation", "}"],
+
+ ["keyword", "public"],
+ ["keyword", "with sharing"],
+ ["keyword", "class"],
+ ["class-name", [
+ "sharingClass"
+ ]],
+ ["punctuation", "{"],
+ ["punctuation", "}"],
+ ["keyword", "public"],
+ ["keyword", "without sharing"],
+ ["keyword", "class"],
+ ["class-name", [
+ "noSharing"
+ ]],
+ ["punctuation", "{"],
+ ["punctuation", "}"],
+ ["keyword", "public"],
+ ["keyword", "inherited sharing"],
+ ["keyword", "class"],
+ ["class-name", [
+ "InheritedSharingClass"
+ ]],
+ ["punctuation", "{"],
+ ["punctuation", "}"]
+]
\ No newline at end of file
diff --git a/tests/languages/apex/comment_feature.test b/tests/languages/apex/comment_feature.test
new file mode 100644
index 0000000000..37590c35d1
--- /dev/null
+++ b/tests/languages/apex/comment_feature.test
@@ -0,0 +1,11 @@
+// comment
+/*
+comment
+*/
+
+----------------------------------------------------
+
+[
+ ["comment", "// comment"],
+ ["comment", "/*\r\ncomment\r\n*/"]
+]
\ No newline at end of file
diff --git a/tests/languages/apex/number_feature.test b/tests/languages/apex/number_feature.test
new file mode 100644
index 0000000000..e2be1e3fd2
--- /dev/null
+++ b/tests/languages/apex/number_feature.test
@@ -0,0 +1,15 @@
+0
+123
+.123
+21.3
+123L
+
+----------------------------------------------------
+
+[
+ ["number", "0"],
+ ["number", "123"],
+ ["number", ".123"],
+ ["number", "21.3"],
+ ["number", "123L"]
+]
diff --git a/tests/languages/apex/operator_feature.test b/tests/languages/apex/operator_feature.test
new file mode 100644
index 0000000000..e7e8464c32
--- /dev/null
+++ b/tests/languages/apex/operator_feature.test
@@ -0,0 +1,64 @@
+=
+
++= *= -= /=
+|= &= <<= >>= >>>=
+
+? :
+
+&& ||
+== === < > <= >= != !==
+
++ - * / !
+++ --
+& |
+^ ^=
+<< >> >>>
+
+?.
+
+----------------------------------------------------
+
+[
+ ["operator", "="],
+
+ ["operator", "+="],
+ ["operator", "*="],
+ ["operator", "-="],
+ ["operator", "/="],
+ ["operator", "|="],
+ ["operator", "&="],
+ ["operator", "<<="],
+ ["operator", ">>="],
+ ["operator", ">>>="],
+
+ ["operator", "?"],
+ ["operator", ":"],
+
+ ["operator", "&&"],
+ ["operator", "||"],
+ ["operator", "=="],
+ ["operator", "==="],
+ ["operator", "<"],
+ ["operator", ">"],
+ ["operator", "<="],
+ ["operator", ">="],
+ ["operator", "!="],
+ ["operator", "!=="],
+
+ ["operator", "+"],
+ ["operator", "-"],
+ ["operator", "*"],
+ ["operator", "/"],
+ ["operator", "!"],
+ ["operator", "++"],
+ ["operator", "--"],
+ ["operator", "&"],
+ ["operator", "|"],
+ ["operator", "^"],
+ ["operator", "^="],
+ ["operator", "<<"],
+ ["operator", ">>"],
+ ["operator", ">>>"],
+
+ ["operator", "?."]
+]
diff --git a/tests/languages/apex/punctuation_feature.test b/tests/languages/apex/punctuation_feature.test
new file mode 100644
index 0000000000..7be7561bec
--- /dev/null
+++ b/tests/languages/apex/punctuation_feature.test
@@ -0,0 +1,16 @@
+( ) [ ] { }
+. , ;
+
+----------------------------------------------------
+
+[
+ ["punctuation", "("],
+ ["punctuation", ")"],
+ ["punctuation", "["],
+ ["punctuation", "]"],
+ ["punctuation", "{"],
+ ["punctuation", "}"],
+ ["punctuation", "."],
+ ["punctuation", ","],
+ ["punctuation", ";"]
+]
diff --git a/tests/languages/apex/sql_feature.test b/tests/languages/apex/sql_feature.test
new file mode 100644
index 0000000000..2ade8e615d
--- /dev/null
+++ b/tests/languages/apex/sql_feature.test
@@ -0,0 +1,48 @@
+b = [SELECT Price__c FROM Book__c WHERE Id =:b.Id];
+return [SELECT Name FROM Contact];
+
+// don't capture array indexing
+a[0].Name = 'Acme';
+
+----------------------------------------------------
+
+[
+ "b ",
+ ["operator", "="],
+ ["sql", [
+ ["punctuation", "["],
+ ["keyword", "SELECT"],
+ " Price__c ",
+ ["keyword", "FROM"],
+ " Book__c ",
+ ["keyword", "WHERE"],
+ " Id ",
+ ["operator", "="],
+ ":b",
+ ["punctuation", "."],
+ "Id",
+ ["punctuation", "]"]
+ ]],
+ ["punctuation", ";"],
+ ["keyword", "return"],
+ ["sql", [
+ ["punctuation", "["],
+ ["keyword", "SELECT"],
+ " Name ",
+ ["keyword", "FROM"],
+ " Contact",
+ ["punctuation", "]"]
+ ]],
+ ["punctuation", ";"],
+
+ ["comment", "// don't capture array indexing"],
+ "\r\na",
+ ["punctuation", "["],
+ ["number", "0"],
+ ["punctuation", "]"],
+ ["punctuation", "."],
+ "Name ",
+ ["operator", "="],
+ ["string", "'Acme'"],
+ ["punctuation", ";"]
+]
\ No newline at end of file
diff --git a/tests/languages/apex/string_feature.test b/tests/languages/apex/string_feature.test
new file mode 100644
index 0000000000..5d6f204ad5
--- /dev/null
+++ b/tests/languages/apex/string_feature.test
@@ -0,0 +1,13 @@
+''
+' '
+'\''
+'foo\nbar'
+
+----------------------------------------------------
+
+[
+ ["string", "''"],
+ ["string", "' '"],
+ ["string", "'\\''"],
+ ["string", "'foo\\nbar'"]
+]
diff --git a/tests/languages/apex/trigger_feature.test b/tests/languages/apex/trigger_feature.test
new file mode 100644
index 0000000000..c59d1f0162
--- /dev/null
+++ b/tests/languages/apex/trigger_feature.test
@@ -0,0 +1,39 @@
+trigger HelloWorldTrigger on Book__c (before insert) {}
+
+trigger T1 on Account (before delete, after delete, after undelete) {}
+
+----------------------------------------------------
+
+[
+ ["keyword", "trigger"],
+ ["trigger", "HelloWorldTrigger"],
+ ["keyword", "on"],
+ ["class-name", [
+ "Book__c"
+ ]],
+ ["punctuation", "("],
+ ["keyword", "before"],
+ ["keyword", "insert"],
+ ["punctuation", ")"],
+ ["punctuation", "{"],
+ ["punctuation", "}"],
+
+ ["keyword", "trigger"],
+ ["trigger", "T1"],
+ ["keyword", "on"],
+ ["class-name", [
+ "Account"
+ ]],
+ ["punctuation", "("],
+ ["keyword", "before"],
+ ["keyword", "delete"],
+ ["punctuation", ","],
+ ["keyword", "after"],
+ ["keyword", "delete"],
+ ["punctuation", ","],
+ ["keyword", "after"],
+ ["keyword", "undelete"],
+ ["punctuation", ")"],
+ ["punctuation", "{"],
+ ["punctuation", "}"]
+]