From 64cd1fe705d83527ac50c5afc906f97a6e9e81a4 Mon Sep 17 00:00:00 2001 From: David Schach Date: Wed, 18 Oct 2023 17:48:24 -0700 Subject: [PATCH] feat: API 59.0 update and refactor --- demo/{highlight.css => scratchpad.css} | 0 demo/scratchpad.html | 1184 ++++++++++++++++++++++++ demo/testcode.css | 163 ++++ demo/testcode.html | 37 +- demo/vs.css | 65 ++ dist/apex.es.min.js | 152 +-- dist/apex.min.js | 154 +-- package-lock.json | 922 +----------------- package.json | 2 +- scripts/cheatsheet.txt | 14 + src/languages/apex.js | 877 +++++++++++++----- test/markup/apex/apexcode.expected.txt | 40 +- 12 files changed, 2324 insertions(+), 1286 deletions(-) rename demo/{highlight.css => scratchpad.css} (100%) create mode 100644 demo/scratchpad.html create mode 100644 demo/testcode.css create mode 100644 demo/vs.css create mode 100644 scripts/cheatsheet.txt diff --git a/demo/highlight.css b/demo/scratchpad.css similarity index 100% rename from demo/highlight.css rename to demo/scratchpad.css diff --git a/demo/scratchpad.html b/demo/scratchpad.html new file mode 100644 index 0000000..8d243c0 --- /dev/null +++ b/demo/scratchpad.html @@ -0,0 +1,1184 @@ + + + + Single block Apex test file + + + + + + + + + +

+      /**
+      Copyright (c) 2016, Salesforce.org
+      All rights reserved.
+  
+      Redistribution and use in source and binary forms, with or without
+      modification, are permitted provided that the following conditions are met:
+  
+      * Redistributions of source code must retain the above copyright
+        notice, this list of conditions and the following disclaimer.
+      * Redistributions in binary form must reproduce the above copyright
+        notice, this list of conditions and the following disclaimer in the
+        documentation and/or other materials provided with the distribution.
+      * Neither the name of Salesforce.org nor the names of
+        its contributors may be used to endorse or promote products derived
+        from this software without specific prior written permission.
+  
+      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+      "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+      LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+      FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+      COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+      INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+      BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+      LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+      CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+      LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+      ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+      POSSIBILITY OF SUCH DAMAGE.
+  **/
+  
+  @SuppressWarnings('PMD.AvoidGlobalModifier, PMD.ExcessiveClassLength')
+  global with sharing class VOL_SharedCode {
+      @TestVisible
+      private static VOL_Access access = VOL_Access.getInstance();
+  
+      // the list of Campaigns that have Volunteer Jobs
+      global List<SelectOption> listSOCampaignsWithJobs {
+          get {
+              List<SelectOption> listSO = new List<SelectOption>();
+              listSO.add(new SelectOption('', ''));
+  
+              // Ensure the user has access to the object before querying
+              if (
+                  Campaign.SObjectType.getDescribe().isAccessible() &&
+                  Campaign.Name.getDescribe().isAccessible() &&
+                  Campaign.IsActive.getDescribe().isAccessible() &&
+                  Campaign.StartDate.getDescribe().isAccessible() &&
+                  Campaign.RecordTypeId.getDescribe().isAccessible()
+              ) {
+                  for (Campaign c : [
+                      SELECT Name, Id
+                      FROM Campaign
+                      WHERE RecordTypeId = :recordtypeIdVolunteersCampaign AND IsActive = TRUE
+                      ORDER BY StartDate DESC, Name ASC
+                      LIMIT 999
+                  ]) {
+                      listSO.add(new SelectOption(c.id, c.name));
+                  }
+              }
+  
+              return listSO;
+          }
+      }
+  
+      // the list of Volunteer Jobs for the specified Campaign
+      global List<SelectOption> listSOVolunteerJobsOfCampaignId(Id campaignId) {
+          List<SelectOption> listSO = new List<SelectOption>();
+          listSO.add(new SelectOption('', ''));
+  
+          // Ensure the user has access to the object before querying
+          if (
+              Volunteer_Job__c.SObjectType.getDescribe().isAccessible() &&
+              Volunteer_Job__c.Name.getDescribe().isAccessible() &&
+              Volunteer_Job__c.Campaign__c.getDescribe().isAccessible()
+          ) {
+              for (Volunteer_Job__c vj : [
+                  SELECT Name, Id
+                  FROM Volunteer_Job__c
+                  WHERE Campaign__c = :campaignId
+                  ORDER BY name
+                  LIMIT 999
+              ]) {
+                  listSO.add(new SelectOption(vj.id, vj.name));
+              }
+          }
+          return listSO;
+      }
+  
+      // the list of Volunteer Job Shifts for the specified Job
+      global List<SelectOption> listSOVolunteerShiftsOfVolunteerJobId( //NOPMD
+          Id volunteerJobId,
+          Date dtStart,
+          Date dtEnd,
+          Boolean fIncludeShiftName,
+          Boolean fIncludeNumberNeeded
+      ) {
+          return listSOVolunteerShiftsOfVolunteerJobIdFormat(
+              volunteerJobId,
+              dtStart,
+              dtEnd,
+              fIncludeShiftName,
+              fIncludeNumberNeeded,
+              null,
+              null
+          );
+      }
+  
+      // list of select options of Shifts for the specified job, using the date & time format strings for the shifts
+      public static List<SelectOption> listSOVolunteerShiftsOfVolunteerJobIdFormat( //NOPMD
+          Id volunteerJobId,
+          Date dtStart,
+          Date dtEnd,
+          Boolean fIncludeShiftName,
+          Boolean fIncludeNumberNeeded,
+          String strDateFormat,
+          String strTimeFormat
+      ) {
+          List<Volunteer_Job__c> listVolunteerJobs = new List<Volunteer_Job__c>();
+  
+          List<SelectOption> listSO = new List<SelectOption>();
+          listSO.add(new SelectOption('', ''));
+  
+          // Ensure the user has access to the object before querying
+          try {
+              UTIL_Describe.checkObjectReadAccess(String.valueOf(Volunteer_Shift__c.SObjectType));
+          } catch (Exception ex) {
+              // we will return an empty list vs throwing an error
+              return listSO;
+          }
+  
+          Boolean canReadDate = Schema.sObjectType.Volunteer_Shift__c.fields.Start_Date_Time__c.isAccessible();
+          Boolean canReadNumberNeeded = Schema.sObjectType.Volunteer_Shift__c.fields.Number_of_Volunteers_Still_Needed__c.isAccessible();
+  
+          // ensure valid date ranges
+          if (dtStart == null) {
+              dtStart = System.today();
+          }
+          if (dtEnd == null) {
+              dtEnd = System.today().addMonths(12);
+          }
+          dtEnd = dtEnd.addDays(1);
+  
+          // get our shifts in a Job query, so we can use our common date/time formatting routine.
+          listVolunteerJobs = [
+              SELECT
+                  Id,
+                  Campaign__r.IsActive,
+                  Campaign__r.Volunteer_Website_Time_Zone__c,
+                  Volunteer_Website_Time_Zone__c,
+                  (
+                      SELECT
+                          Id,
+                          Name,
+                          Start_Date_Time__c,
+                          Duration__c,
+                          Number_of_Volunteers_Still_Needed__c,
+                          Description__c,
+                          System_Note__c
+                      FROM Volunteer_Job_Slots__r
+                      WHERE Start_Date_Time__c >= :dtStart AND Start_Date_Time__c < :dtEnd
+                      ORDER BY Start_Date_Time__c
+                      LIMIT 999
+                  )
+              FROM Volunteer_Job__c
+              WHERE Id = :volunteerJobId
+          ];
+  
+          // bail out if no jobs found
+          if (listVolunteerJobs.size() == 0) {
+              return listSO;
+          }
+  
+          // whether to use our datetime formatting, or salesforce default for the current user
+          Boolean useDateTimeFixup = (strDateFormat != null && strTimeFormat != null);
+  
+          // put correct date/time format with appropriate timezone in system note field (in memory only)
+          if (useDateTimeFixup) {
+              {
+                  dateTimeFixup(listVolunteerJobs, strDateFormat, strTimeFormat);
+              }
+          }
+  
+          for (Volunteer_Shift__c vs : listVolunteerJobs[0].Volunteer_Job_Slots__r) {
+              SelectOption so = new SelectOption(
+                  vs.id,
+                  (canReadDate ? (useDateTimeFixup ? vs.System_Note__c : vs.Start_Date_Time__c.format()) : '') +
+                      (fIncludeShiftName ? '    (' + vs.name + ')' : '') +
+                      (fIncludeNumberNeeded && canReadNumberNeeded
+                          ? '  ' +
+                            (vs.Number_of_Volunteers_Still_Needed__c > 0
+                                ? System.Label.labelCalendarStillNeeded + vs.Number_of_Volunteers_Still_Needed__c
+                                : System.Label.labelCalendarShiftFull) +
+                            ' '
+                          : '')
+              );
+              so.setEscapeItem(false);
+              listSO.add(so);
+          }
+          return listSO;
+      }
+  
+      // routine to go through all the shifts, and create the display
+      // for the shifts start date & time - end date & time, using the appropriate
+      // time zone that might be specified on the Job, Campaign, or Site Guest User.
+      // Note that it stores this  in the Shift's System_Note__c field (in memory only).
+      public static void dateTimeFixup(List<Volunteer_Job__c> listJob, String strDateFormat, String strTimeFormat) {
+          // get default time zone for site guest user
+          User u = [SELECT TimeZoneSidKey FROM User WHERE Id = :Userinfo.getUserId()];
+  
+          // javascript formatting used 'tt' for am/pm, whereas apex formatting uses 'a'.
+          String strFormat = strDateFormat + ' ' + strTimeFormat.replace('tt', 'a');
+          String strFormatEndTime = strTimeFormat.replace('tt', 'a');
+  
+          for (Volunteer_Job__c job : listJob) {
+              String strTimeZone = job.Volunteer_Website_Time_Zone__c;
+              if (strTimeZone == null) {
+                  strTimeZone = job.Campaign__r.Volunteer_Website_Time_Zone__c;
+              }
+              if (strTimeZone == null) {
+                  strTimeZone = u.TimeZoneSidKey;
+              }
+              for (Volunteer_Shift__c shift : job.Volunteer_Job_Slots__r) {
+                  DateTime dtEnd = shift.Start_Date_Time__c.addMinutes(Integer.valueOf(shift.Duration__c * 60));
+                  String strStart = shift.Start_Date_Time__c.format(strFormat, strTimeZone);
+  
+                  // see if start and end are on the same day
+                  if (shift.Start_Date_Time__c.format('d', strTimeZone) == dtEnd.format('d', strTimeZone)) {
+                      shift.System_Note__c = strStart + ' - ' + dtEnd.format(strFormatEndTime, strTimeZone);
+                  } else {
+                      shift.System_Note__c = strStart + ' - ' + dtEnd.format(strFormat, strTimeZone);
+                  }
+              }
+          }
+      }
+  
+      // return the GMT datetime for a datetime in the specified timezone
+      public static DateTime dtGmtFromDtTimeZone(DateTime dt, TimeZone tz) {
+          Integer offset = tz.getOffset(dt);
+          return dt.addSeconds(-offset / 1000);
+      }
+  
+      // Volunteer Custom Settings object.  Loads an existing, and if not found creates one with default values.
+      global static Volunteers_Settings__c VolunteersSettings {
+          get {
+              if (VolunteersSettings == null) {
+                  VolunteersSettings = Volunteers_Settings__c.getInstance();
+  
+                  if (VolunteersSettings.Id == null) {
+                      VolunteersSettings = Volunteers_Settings__c.getOrgDefaults();
+                  }
+  
+                  if (VolunteersSettings.Id == null) {
+                      VolunteersSettings.Setupownerid = UserInfo.getOrganizationId();
+  
+                      // create reasonable defaults
+                      VolunteersSettings.Signup_Matches_Existing_Contacts__c = false;
+                      VolunteersSettings.Signup_Creates_Contacts_If_No_Match__c = false;
+                      VolunteersSettings.Signup_Bucket_Account_On_Create__c = null;
+                      VolunteersSettings.Recurring_Job_Future_Months__c = 4;
+                      VolunteersSettings.Grant_Guest_Users_Update_Access__c = false;
+                      VolunteersSettings.Contact_Match_Email_Fields__c = null;
+                      VolunteersSettings.Contact_Match_First_Name_Fields__c = null;
+                      VolunteersSettings.Personal_Site_Org_Wide_Email_Name__c = null;
+                      VolunteersSettings.Contact_Matching_Rule__c = 'Firstname;Lastname;Email';
+                      VolunteersSettings.Personal_Site_Report_Hours_Filtered__c = false;
+                      if (UTIL_Describe.hasObjectCreateAccess(UTIL_Describe.StrTokenNSPrefix('Volunteers_Settings__c'))) {
+                          insert VolunteersSettings;
+                      }
+                  } else if (VolunteersSettings.Contact_Matching_Rule__c == null) {
+                      VolunteersSettings.Contact_Matching_Rule__c = 'Firstname;Lastname;Email';
+                      if (UTIL_Describe.hasObjectUpdateAccess(UTIL_Describe.StrTokenNSPrefix('Volunteers_Settings__c'))) {
+                          update VolunteersSettings;
+                      }
+                  }
+              }
+              return VolunteersSettings;
+          }
+          set;
+      }
+  
+      // helper to get the AccoutId of the Bucket Account specified in Custom Settings.
+      global static Id settingsBucketAccountId {
+          get {
+              if (settingsBucketAccountId == null) {
+                  if (
+                      VolunteersSettings.Signup_Bucket_Account_On_Create__c != null &&
+                      Account.getSObjectType().getDescribe().isAccessible()
+                  ) {
+                      Account[] acc = [
+                          SELECT Id
+                          FROM Account
+                          WHERE name = :VolunteersSettings.Signup_Bucket_Account_On_Create__c
+                          LIMIT 1
+                      ];
+                      if (acc.size() > 0) {
+                          settingsBucketAccountId = acc[0].Id;
+                      }
+                  }
+              }
+              return settingsBucketAccountId;
+          }
+          set;
+      }
+  
+      // test helper that allows one to override the users's Custom Settings with the settings we want to test with.
+      global static Volunteers_Settings__c getVolunteersSettingsForTests(Volunteers_Settings__c mySettings) {
+          //clear out whatever settings exist
+          delete [SELECT Id FROM Volunteers_Settings__c];
+          SettingsBucketAccountId = null;
+  
+          //create our own based on what's passed in from the test
+          VolunteersSettings = new Volunteers_Settings__c(
+              Signup_Matches_Existing_Contacts__c = mySettings.Signup_Matches_Existing_Contacts__c,
+              Signup_Creates_Contacts_If_No_Match__c = mySettings.Signup_Creates_Contacts_If_No_Match__c,
+              Signup_Bucket_Account_On_Create__c = mySettings.Signup_Bucket_Account_On_Create__c,
+              Recurring_Job_Future_Months__c = mySettings.Recurring_Job_Future_Months__c,
+              Contact_Match_Email_Fields__c = mySettings.Contact_Match_Email_Fields__c,
+              Contact_Match_First_Name_Fields__c = mySettings.Contact_Match_First_Name_Fields__c,
+              Contact_Matching_Rule__c = mySettings.Contact_Matching_Rule__c,
+              Personal_Site_Org_Wide_Email_Name__c = mySettings.Personal_Site_Org_Wide_Email_Name__c,
+              Personal_Site_Report_Hours_Filtered__c = mySettings.Personal_Site_Report_Hours_Filtered__c
+          );
+  
+          insert VolunteersSettings;
+          return VolunteersSettings;
+      }
+  
+      // global helper to get the Volunteers Campaign recordtype.
+      private class MyException extends Exception {
+      }
+      global static Id recordtypeIdVolunteersCampaign {
+          get {
+              if (recordtypeIdVolunteersCampaign == null) {
+                  List<RecordType> listRT = [SELECT Id FROM RecordType WHERE DeveloperName = 'Volunteers_Campaign'];
+                  if (listRT.size() == 0) {
+                      throw (new MyException('The Volunteers Campaign Record Type is missing and must be restored.'));
+                  }
+                  recordtypeIdVolunteersCampaign = listRT[0].Id;
+              }
+              return recordtypeIdVolunteersCampaign;
+          }
+          set;
+      }
+  
+      // shared routine to get all Fields names from the specified Field Set on Contact
+      // also explicitly adds additional Contact fields that we will always use in our Sites pages.
+      global static List<String> listStrFieldsFromContactFieldSet(Schema.FieldSet fs) {
+          Set<String> setStrFields = new Set<String>();
+          for (Schema.FieldSetMember f : fs.getFields()) {
+              setStrFields.add(f.getFieldPath().toLowerCase());
+          }
+          // also add the fields we explicitly refer to in CreateOrUpdateContactFS()
+          // we use a set (with lowercase) to avoid creating duplicates.
+          setStrFields.add('firstname');
+          setStrFields.add('lastname');
+          setStrFields.add('email');
+          //setStrFields.add(VOL_SharedCode.StrTokenNSPrefix('volunteer_status__c').tolowerCase());
+          //setStrFields.add(VOL_SharedCode.StrTokenNSPrefix('volunteer_notes__c').toLowerCase());
+          List<String> listStrFields = new List<String>();
+          listStrFields.addAll(setStrFields);
+          return listStrFields;
+      }
+  
+      // shared routine to get all Fields names form the specified Field Set
+      global static List<String> listStrFieldsFromFieldSet(Schema.FieldSet fs) {
+          List<String> listStrFields = new List<String>();
+          for (Schema.FieldSetMember f : fs.getFields()) {
+              listStrFields.add(f.getFieldPath());
+          }
+          return listStrFields;
+      }
+  
+      // global code to create a new lead or contact for web volunteer signup.
+      // this code is used by both the VolunteersSignup page, and the VolunteersJobListing page.
+      // it uses the custom setting for the bucket account, but takes parameters for
+      // matching existing contacts, and create contacts vs. leads.  this is because the two pages have different use cases.
+      // it also assumes that the contact that is passed in is the dummy record from the web page, and thus isn't real, and
+      // uses the Department field to track the user's company name.
+      global static Id createContactOrLead(Contact contact, Boolean fMatchExistingContacts, Boolean fCreateContacts) {
+          // update the date before we start
+          contact.Volunteer_Last_Web_Signup_Date__c = System.today();
+  
+          // let's see if we can find any matching Contacts.
+          List<Contact> listCon = [
+              SELECT
+                  Id,
+                  Lastname,
+                  Firstname,
+                  Email,
+                  Phone,
+                  HomePhone,
+                  Volunteer_Availability__c,
+                  Volunteer_Notes__c,
+                  Volunteer_Last_Web_Signup_Date__c,
+                  Volunteer_Status__c,
+                  Volunteer_Skills__c,
+                  Volunteer_Organization__c
+              FROM Contact
+              WHERE Lastname = :contact.Lastname AND Firstname = :contact.Firstname AND Email = :contact.Email
+          ];
+  
+          Set<String> setFlds = new Set<String>{
+              'Lastname',
+              'Firstname',
+              'Email',
+              'Phone',
+              'HomePhone',
+              UTIL_Describe.StrTokenNSPrefix('Volunteer_Availability__c'),
+              UTIL_Describe.StrTokenNSPrefix('Volunteer_Notes__c'),
+              UTIL_Describe.StrTokenNSPrefix('Volunteer_Last_Web_Signup_Date__c'),
+              UTIL_Describe.StrTokenNSPrefix('Volunteer_Status__c'),
+              UTIL_Describe.StrTokenNSPrefix('Volunteer_Skills__c'),
+              UTIL_Describe.StrTokenNSPrefix('Volunteer_Organization__c')
+          };
+  
+          // if we can match existing contacts, and we found a match, update them.
+          if (fMatchExistingContacts && listCon.size() > 0) {
+              for (Contact con : listCon) {
+                  con.Volunteer_Last_Web_Signup_Date__c = contact.Volunteer_Last_Web_Signup_Date__c;
+                  con.Volunteer_Availability__c = contact.Volunteer_Availability__c;
+                  String strNotes = con.Volunteer_Notes__c;
+                  if (strNotes != '') {
+                      strNotes += '  ';
+                  }
+                  if (contact.Volunteer_Notes__c != null) {
+                      con.Volunteer_Notes__c =
+                          strNotes +
+                          '[' +
+                          String.valueOf(System.today()) +
+                          ']: ' +
+                          contact.Volunteer_Notes__c;
+                  }
+                  con.Volunteer_Skills__c = contact.Volunteer_Skills__c;
+                  if (con.Volunteer_Status__c == null) {
+                      con.Volunteer_Status__c = 'New Sign Up';
+                  }
+                  if (contact.Phone != null) {
+                      con.Phone = contact.Phone;
+                  }
+                  if (contact.HomePhone != null) {
+                      con.HomePhone = contact.HomePhone;
+                  } // NOTE: if we find existing contact(s), we don't worry about doing anything with Company.
+                  // but we can at least put it in the new Volunteer_Organization__c field.
+                  if (contact.Department != null) {
+                      con.Volunteer_Organization__c = contact.Department;
+                  }
+              }
+  
+              checkUpdateAccessSites('Contact', setFlds);
+              access.updateRecords(listCon, dmlDuplicateOptions);
+  
+              return listCon[0].Id;
+          } else if (fCreateContacts) {
+              // No Match found, create a Contact
+              contact.LeadSource = 'Web Volunteer Signup';
+              contact.Volunteer_Status__c = 'New Sign Up';
+  
+              Account accToUse = null;
+  
+              // see if we can find their company (which we assume the form used Department to record.)
+              if (contact.Department != null && Account.getSObjectType().getDescribe().isAccessible()) {
+                  List<Account> listAccount = [SELECT Id, Name FROM Account WHERE Name = :contact.Department LIMIT 1];
+                  if (listAccount.size() > 0) {
+                      accToUse = listAccount.get(0);
+                  }
+                  contact.Volunteer_Organization__c = contact.Department;
+              }
+  
+              // if company found, use it
+              if (accToUse != null) {
+                  contact.AccountId = accToUse.Id;
+              } else {
+                  // otherwise use the bucket account (which may be null and imply the 1:1 model in NPSP)
+                  contact.AccountId = VOL_SharedCode.SettingsBucketAccountId;
+              }
+              access.checkCreateAccess('Contact', setFlds);
+              access.insertRecords(new List<Contact>{ contact }, dmlDuplicateOptions);
+              return contact.Id;
+          } else {
+              // No Match found, create a Lead
+              Lead ld = new lead();
+              ld.FirstName = contact.FirstName;
+              ld.LastName = contact.LastName;
+              ld.Company = (contact.Department == null ? '[not provided]' : contact.Department);
+              ld.Email = contact.Email;
+              ld.Phone = contact.Phone;
+              ld.MobilePhone = contact.HomePhone; // leads don't have a home phone!
+              ld.Volunteer_Availability__c = contact.Volunteer_Availability__c;
+              ld.Volunteer_Notes__c = contact.Volunteer_Notes__c;
+              ld.Volunteer_Skills__c = contact.Volunteer_Skills__c;
+              ld.Volunteer_Status__c = 'New Sign Up';
+              ld.LeadSource = 'Web Volunteer Signup';
+              UTIL_Describe.checkCreateAccess(
+                  'Lead',
+                  new Set<String>{
+                      'FirstName',
+                      'LastName',
+                      'Company',
+                      'Email',
+                      'Phone',
+                      'MobilePhone',
+                      'LeadSource',
+                      UTIL_Describe.StrTokenNSPrefix('Volunteer_Availability__c'),
+                      UTIL_Describe.StrTokenNSPrefix('Volunteer_Notes__c'),
+                      UTIL_Describe.StrTokenNSPrefix('Volunteer_Skills__c'),
+                      UTIL_Describe.StrTokenNSPrefix('Volunteer_Status__c')
+                  }
+              );
+              Database.insert(ld, dmlDuplicateOptions);
+              return ld.Id;
+          }
+      }
+  
+      // global code to verify the passed in ContactId is valid, as well as the email
+      // exists on the Contact record.
+      global static Boolean isValidContactIdAndEmail(Id contactId, String strEmail) {
+          if (!Contact.getSObjectType().getDescribe().isAccessible()) {
+              return false;
+          }
+  
+          String strSoql = 'SELECT Id from Contact where Id = :contactId ';
+          if (VolunteersSettings.Personal_Site_Requires_URL_Email_Match__c) {
+              if (strEmail == null || strEmail == '' || !Contact.Email.getDescribe().isAccessible()) {
+                  return false;
+              }
+              strEmail = strEmail.escapeHtml4();
+              strSoql += 'AND (Email = :strEmail';
+              // any additional email fields to check
+              Map<String, SObjectField> fieldByName = Contact.getSObjectType().getDescribe().fields.getMap();
+              if (VolunteersSettings.Contact_Match_Email_Fields__c != null) {
+                  List<String> listStrEmail = new List<String>();
+                  listStrEmail = VolunteersSettings.Contact_Match_Email_Fields__c.split(';');
+  
+                  for (String str : listStrEmail) {
+                      if (fieldByName.containsKey(str)) {
+                          strSoql += ' or ' + str + ' = :strEmail ';
+                      }
+                  }
+              }
+              // handle NPSP email fields
+              if (IsNPSPInstalled) {
+                  if (
+                      fieldByName.containsKey('npe01__AlternateEmail__c') &&
+                      fieldByName.get('npe01__AlternateEmail__c').getDescribe().isAccessible()
+                  ) {
+                      strSoql += ' or npe01__AlternateEmail__c = :strEmail ';
+                  }
+                  if (
+                      fieldByName.containsKey('npe01__HomeEmail__c') &&
+                      fieldByName.get('npe01__HomeEmail__c').getDescribe().isAccessible()
+                  ) {
+                      strSoql += ' or npe01__HomeEmail__c = :strEmail ';
+                  }
+                  if (
+                      fieldByName.containsKey('npe01__WorkEmail__c') &&
+                      fieldByName.get('npe01__WorkEmail__c').getDescribe().isAccessible()
+                  ) {
+                      strSoql += ' or npe01__WorkEmail__c = :strEmail ';
+                  }
+              }
+              strSoql += ') ';
+          }
+          List<Contact> listCon = Database.query(strSoql);
+          return listCon.size() > 0;
+      }
+  
+      // global code to lookup an existing contact
+      // listStrFields are optional fields to include in the soql call
+      global static List<Contact> lookupContact(Contact contactRecord, List<String> listStrFields) {
+          // let's see if we can find any matching Contacts.
+          // we need to use dynamic soql, since we allow the user to modify the FieldSet of fields to edit.
+          String strSoql = 'SELECT ';
+          String strComma = '';
+          if (listStrFields == null) {
+              strSoql += 'Id';
+          } else {
+              for (String strF : listStrFields) {
+                  strSoql += strComma + strF;
+                  strComma = ', ';
+              }
+          }
+          strSoql += ' from Contact ';
+          String strAnd = ' where ';
+  
+          // make sure their settings haven't been completely cleared
+          if (VolunteersSettings.Contact_Matching_Rule__c == null || VolunteersSettings.Contact_Matching_Rule__c == '') {
+              VolunteersSettings.Contact_Matching_Rule__c = 'Firstname;Lastname;Email';
+          }
+          final String rule = VolunteersSettings.Contact_Matching_Rule__c;
+          if (rule.containsIgnoreCase(String.valueOf(Contact.LastName))) {
+              strSoql += strAnd + ' (Lastname=\'' + StrEscape(contactRecord.LastName) + '\') ';
+              strAnd = ' and ';
+          }
+          if (rule.containsIgnoreCase(String.valueOf(Contact.FirstName))) {
+              strSoql += strAnd + ' (Firstname=\'' + StrEscape(contactRecord.FirstName) + '\'';
+              strAnd = ' and ';
+  
+              // any additional firstname fields to check
+              if (VolunteersSettings.Contact_Match_First_Name_Fields__c != null) {
+                  List<String> listStrFname = new List<String>();
+                  listStrFname = VolunteersSettings.Contact_Match_First_Name_Fields__c.split(';');
+                  for (String str : listStrFname) {
+                      strSoql += ' or ' + str + '=\'' + StrEscape(contactRecord.FirstName) + '\'';
+                  }
+              }
+              strSoql += ') ';
+          }
+          if (rule.containsIgnoreCase(String.valueOf(Contact.Email))) {
+              strSoql += strAnd + ' (Email=\'' + contactRecord.Email + '\'';
+              strAnd = ' and ';
+              // any additional email fields to check
+              if (VolunteersSettings.Contact_Match_Email_Fields__c != null) {
+                  List<String> listStrEmail = new List<String>();
+                  listStrEmail = VolunteersSettings.Contact_Match_Email_Fields__c.split(';');
+                  for (String str : listStrEmail) {
+                      strSoql += ' or ' + str + '=\'' + contactRecord.Email + '\'';
+                  }
+              }
+              // handle NPSP email fields
+              if (IsNPSPInstalled) {
+                  strSoql += ' or npe01__AlternateEmail__c=\'' + contactRecord.Email + '\'';
+                  strSoql += ' or npe01__HomeEmail__c=\'' + contactRecord.Email + '\'';
+                  strSoql += ' or npe01__WorkEmail__c=\'' + contactRecord.Email + '\'';
+              }
+              strSoql += ') ';
+          }
+          strSoql += ' limit 999 ';
+          List<Contact> listCon = Database.query(strSoql);
+          return listCon;
+      }
+  
+      // global code to create a new contact, or update an existing contact, for web volunteer signup.
+      // this code is used by both the VolunteersSignupFS page, and the VolunteersJobListingFS page.
+      // if creating a new Contact, it uses the custom setting for the bucket account, but takes parameters for
+      // the account name to try to lookup and match.
+      // It also takes the list of fields on the contact object to copy over.
+      global static Id createOrUpdateContactFS( //NOPMD
+          String contactIdExisting,
+          Contact contact,
+          String strAccountName,
+          List<String> listStrFields
+      ) {
+          return CreateOrUpdateContactFS(contactIdExisting, contact, strAccountName, listStrFields, true);
+      }
+  
+      global static Id createOrUpdateContactFS( //NOPMD
+          String contactIdExisting,
+          Contact contact,
+          String strAccountName,
+          List<String> listStrFields,
+          Boolean setLastWebSignup
+      ) {
+          // listStrFields is the specific list of fields on the form's fieldset, which we should assume we want to save.
+          // we also need to special case several fields we will potentially set, but we also need to know,
+          // if they are in the fieldset or not.
+          Set<String> setStrFields = new Set<String>();
+  
+          // store all fields in lower case
+          for (String strField : listStrFields) {
+              setStrFields.add(strField.toLowerCase());
+          }
+          Boolean isStatusInFS = !setStrFields.add(VOL_SharedCode.StrTokenNSPrefix('volunteer_status__c').tolowerCase());
+          Boolean isNotesInFS = !setStrFields.add(VOL_SharedCode.StrTokenNSPrefix('volunteer_notes__c').toLowerCase());
+  
+          // create a new list with all the fields
+          List<String> listStrFieldsAll = new List<String>(setStrFields);
+  
+          // we will check perms on the fields in this set, so remove Id
+          setStrFields.remove('id');
+  
+          List<Contact> listCon = LookupContact(contact, listStrFieldsAll);
+  
+          // if we found a match
+          if (listCon.size() > 0) {
+              Contact conExisting = null;
+  
+              // match the one that has the same Id
+              if (contactIdExisting != null && contactIdExisting != '') {
+                  for (Integer i = 0; i < listCon.size(); i++) {
+                      if (listCon[i].Id == contactIdExisting) {
+                          conExisting = listCon[i];
+                      }
+                  }
+              }
+              // use first one if no match found.
+              if (conExisting == null) {
+                  conExisting = listCon[0];
+              }
+  
+              // special case appending Volunteer Notes, rather than overwriting.
+              if (isNotesInFS) {
+                  if (
+                      contact.Volunteer_Notes__c != null &&
+                      contact.Volunteer_Notes__c != conExisting.Volunteer_Notes__c
+                  ) {
+                      contact.Volunteer_Notes__c =
+                          (conExisting.Volunteer_Notes__c != null ? (conExisting.Volunteer_Notes__c + '  ') : '') +
+                          '[' +
+                          String.valueOf(System.today()) +
+                          ']: ' +
+                          contact.Volunteer_Notes__c;
+                  } else {
+                      contact.Volunteer_Notes__c = conExisting.Volunteer_Notes__c;
+                  }
+              }
+  
+              // special case setting Volunteer Status, only if not currently set.
+              if (conExisting.Volunteer_Status__c != null) {
+                  contact.Volunteer_Status__c = null;
+              } else {
+                  conExisting.Volunteer_Status__c = 'New Sign Up';
+              }
+  
+              // now copy over all the non-null fields from the form's contact to the existing contact.
+              // avoid overwriting existing first name or existing email, since we might match it from in a different field.
+              // special case address fields
+              Boolean hasMailingAddress = false;
+              Boolean hasOtherAddress = false;
+              for (String strF : listStrFields) {
+                  strF = strF.toLowerCase();
+                  // we actually still want to copy email if it is currently empty
+                  if (strF == 'email' && conExisting.Email == null) {
+                      conExisting.Email = contact.Email;
+                      continue;
+                  }
+                  if (strF != 'Id' && strF != 'firstname' && strF != 'email' && contact.get(strF) != null) {
+                      if (isMailingAddressField(strF)) {
+                          hasMailingAddress = true;
+                          continue;
+                      }
+                      if (isOtherAddressField(strF)) {
+                          hasOtherAddress = true;
+                          continue;
+                      }
+                      conExisting.put(strF, contact.get(strF));
+                  }
+              }
+              if (hasMailingAddress) {
+                  VOL_StateCountryPicklists.copyAddressStdSObj(contact, 'Mailing', conExisting, 'Mailing');
+              }
+              if (hasOtherAddress) {
+                  VOL_StateCountryPicklists.copyAddressStdSObj(contact, 'Other', conExisting, 'Other');
+              }
+              if (setLastWebSignup) {
+                  conExisting.Volunteer_Last_Web_Signup_Date__c = System.today();
+              }
+              checkUpdateAccessSites('Contact', setStrFields);
+              access.updateRecords(new List<Contact>{ conExisting }, dmlDuplicateOptions);
+  
+              // null out notes, so another update won't append them again!
+              contact.Volunteer_Notes__c = null;
+              return conExisting.Id;
+          } else {
+              // No Match found, create a Contact
+  
+              // don't assume the contact object wasn't already used.
+              // since we can't null out Id for the insert, copy all
+              // the fields to a new object and use it.
+              Contact conNew = new Contact();
+              //  now copy over all the non-null fields from the form's contact to the existing contact.
+              // special case address fields
+              Boolean hasMailingAddress = false;
+              Boolean hasOtherAddress = false;
+              for (String strF : listStrFields) {
+                  strF = strF.toLowerCase();
+                  if (strF != 'Id' && contact.get(strF) != null) {
+                      if (isMailingAddressField(strF)) {
+                          hasMailingAddress = true;
+                          continue;
+                      }
+                      if (isOtherAddressField(strF)) {
+                          hasOtherAddress = true;
+                          continue;
+                      }
+                      conNew.put(strF, contact.get(strF));
+                  }
+              }
+              if (hasMailingAddress) {
+                  VOL_StateCountryPicklists.copyAddressStdSObj(contact, 'Mailing', conNew, 'Mailing');
+              }
+              if (hasOtherAddress) {
+                  VOL_StateCountryPicklists.copyAddressStdSObj(contact, 'Other', conNew, 'Other');
+              }
+              // see if we can find their company
+              Account accToUse = null;
+              if (strAccountName != null && Account.getSObjectType().getDescribe().isAccessible()) {
+                  strAccountName = strAccountName.escapeHtml4();
+                  List<Account> listAccount = [SELECT Id, Name FROM Account WHERE Name = :strAccountName LIMIT 1];
+                  if (listAccount.size() > 0) {
+                      accToUse = listAccount.get(0);
+                  }
+              }
+  
+              // if company found, use it
+              if (accToUse != null) {
+                  conNew.AccountId = accToUse.Id;
+              } else {
+                  // otherwise use the bucket account (which may be null and imply the 1:1 model in NPSP)
+                  conNew.AccountId = VOL_SharedCode.SettingsBucketAccountId;
+              }
+  
+              if (setLastWebSignup) {
+                  conNew.Volunteer_Last_Web_Signup_Date__c = System.today();
+              }
+              conNew.LeadSource = 'Web Volunteer Signup';
+              conNew.Volunteer_Status__c = 'New Sign Up';
+  
+              access.checkCreateAccess('Contact', setStrFields);
+              access.insertRecords(new List<Contact>{ conNew }, dmlDuplicateOptions);
+  
+              // null out notes, so another update won't append them again!
+              contact.Volunteer_Notes__c = null;
+              return conNew.Id;
+          }
+      }
+  
+      /*******************************************************************************************************
+       * @description detects standard mailing address fields
+       * @param strField The field to check against
+       * @return Boolean True if it is a standard mailing address field, false if not.
+       */
+      public static Boolean isMailingAddressField(String strField) {
+          return (strField.containsIgnoreCase('mailing') && !strField.endsWith('__c'));
+      }
+  
+      /*******************************************************************************************************
+       * @description detects standard other address fields
+       * @param strField The field to check against
+       * @return Boolean True if it is a standard other address field, false if not.
+       */
+      public static Boolean isOtherAddressField(String strField) {
+          return (strField.containsIgnoreCase('other') &&
+          !strField.endsWith('__c') &&
+          !strField.equalsIgnoreCase('otherphone'));
+      }
+  
+      // global utility to escape a .
+      global static String strEscape(String str) {
+          if (str == null) {
+              return null;
+          }
+          return String.escapeSingleQuotes(str);
+      }
+  
+      // global utility to load up an existing object and copy it to the provided object
+      global static void loadAndCopyObject(Id Id, SObject sobj) {
+          loadAndCopyObject(id, sobj, null);
+      }
+  
+      public static SObject loadAndCopyObject(Id Id, SObject sobj, List<String> listStrFields) {
+          Schema.DescribeSObjectResult des = sobj.getSObjectType().getDescribe();
+  
+          // if fields not provided, get all Contact fields
+          if (listStrFields == null) {
+              listStrFields = new List<String>();
+              // get the fields for the object
+              Map<String, Schema.SObjectField> mapS = des.fields.getMap().clone();
+              // avoid any of the API version 30 compound fields
+              // we only worry about Contact ones, since all callers are giving us contacts to copy.
+              mapS.remove('mailingaddress');
+              mapS.remove('otheraddress');
+              listStrFields.addAll(mapS.keySet());
+          }
+  
+          String strSoql = 'SELECT ';
+          String strComma = '';
+          for (String strF : listStrFields) {
+              strSoql += strComma + strF;
+              strComma = ', ';
+          }
+          strSoql += ' from ' + des.getName() + ' where Id = :id ';
+          strSoql += ' limit 1';
+          List<SObject> listSObj = Security.stripInaccessible(AccessType.READABLE, Database.query(strSoql)).getRecords();
+  
+          if (listSObj.size() > 0) {
+              SObject sobjT = listSObj[0];
+              //  now copy over all the non-null fields from the form's contact to the existing contact.
+              for (String strF : listStrFields) {
+                  if (sobjT.get(strF) != null) {
+                      try {
+                          sobj.put(strF, sobjT.get(strF));
+                      }
+                      // prettier-ignore
+                      catch (exception ex) { //NOPMD
+                      }
+                  }
+              }
+              return sobjT;
+          }
+          return null;
+      }
+  
+      public static void volunteerHoursTrigger(
+          List<Volunteer_Hours__c> listHoursOld,
+          List<Volunteer_Hours__c> listHoursNew,
+          Boolean resetTotals
+      ) {
+          // consider both newMap and oldMap.
+          // for each hours object, there are two potential shifts it interacts with.
+          // within a batch of hours changes (import scenario), multiple hours can affect the same shift.
+          // thus need to keep track of the shifts to update, their original value, and the sum of their changed values.
+  
+          // Insert scenario: status=Confirmed or Completed. Shift <> null. Number of Volunteers <> null.
+          // Delete scenario: status=Confirmed or Completed.  Shift <> null. Number of Volunteers <> null.
+          // Update scenario: just treat as a delete and an insert, since we already have to handle multiple changes to same job!
+  
+          // WARNING: deleting, undeleting, or merging a Contact, does NOT call any trigger on the Hours!
+          // thus I've manually called this from the before delete & after undelete trigger on Contacts (VOL_Contact_MaintainHours).
+          Map<Id, Double> mpShiftIdDelta = new Map<Id, Double>();
+  
+          // first we go through the new hours, and add up the number of volunteers per shift
+          if (listHoursNew != null) {
+              for (Volunteer_Hours__c hr : listHoursNew) {
+                  if (
+                      (hr.Status__c == 'Confirmed' || hr.Status__c == 'Completed') &&
+                      (hr.Volunteer_Shift__c <> null &&
+                      hr.Number_Of_Volunteers__c != null)
+                  ) {
+                      Double numVols = mpShiftIdDelta.get(hr.Volunteer_Shift__c);
+                      if (numVols == null) {
+                          numVols = 0;
+                      }
+                      numVols += hr.Number_of_Volunteers__c;
+                      mpShiftIdDelta.put(hr.Volunteer_Shift__c, numVols);
+                  }
+              }
+          }
+  
+          // second we go through the old hours, and subtract the number of volunteers per shift
+          if (listHoursOld != null) {
+              for (Volunteer_Hours__c hr : listHoursOld) {
+                  if (
+                      (hr.Status__c == 'Confirmed' || hr.Status__c == 'Completed') &&
+                      (hr.Volunteer_Shift__c <> null &&
+                      hr.Number_Of_Volunteers__c != null)
+                  ) {
+                      Double numVols = mpShiftIdDelta.get(hr.Volunteer_Shift__c);
+                      if (numVols == null) {
+                          numVols = 0;
+                      }
+                      numVols -= hr.Number_of_Volunteers__c;
+                      mpShiftIdDelta.put(hr.Volunteer_Shift__c, numVols);
+                  }
+              }
+          }
+  
+          // bail out if nothing found (to avoid runtime error!)
+          if (mpShiftIdDelta.size() == 0) {
+              return;
+          }
+          // now that we have the Id's of the shifts, let's get them from the database, update them by the number of volunteers, and then commit.
+          //List<Volunteer_Shift__c> listShifts = new List<Volunteer_Shift__c>();
+          for (List<Volunteer_Shift__c> listShifts : [
+              SELECT Id, Total_Volunteers__c
+              FROM Volunteer_Shift__c
+              WHERE Id IN :mpShiftIdDelta.keySet()
+          ]) {
+              // loop through and update them
+              for (Volunteer_Shift__c shift : listShifts) {
+                  Double numVols = shift.Total_Volunteers__c;
+                  if (numVols == null || resetTotals) {
+                      numVols = 0;
+                  }
+                  shift.Total_Volunteers__c = numVols + mpShiftIdDelta.get(shift.Id);
+              }
+              // Note: We will not check CRUD / FLS for this process so that rollups are always in sync regardless of user
+              access.updateRecords(listShifts);
+          }
+      }
+  
+      // global utility used to detect whether the Non Profit Starter Pack is installed in this instance.
+      private static Boolean fCheckedForNPSP = false;
+      global static Boolean IsNPSPInstalled {
+          get {
+              if (!fCheckedForNPSP) {
+                  Schema.SObjectType token = Schema.getGlobalDescribe().get('npe01__OppPayment__c');
+                  IsNPSPInstalled = (token != null);
+                  fCheckedForNPSP = true;
+              }
+              return IsNPSPInstalled;
+          }
+          set;
+      }
+  
+      // global utility used to detect whether the Volunteers is running in a managed instance or unmanaged instance
+      private static Boolean fCheckedForVolunteersNamespace = false;
+      global static Boolean IsManagedCode {
+          get {
+              if (!fCheckedForVolunteersNamespace) {
+                  // in order for this call to work as expected, we must be API Version 28, but we
+                  // want to stay at version 25, so let's find another way!!
+                  //Schema.SObjectType token = Schema.getGlobalDescribe().get('GW_Volunteers__Volunteer_Job__c');
+                  //IsManagedCode = (token != null);
+  
+                  IsManagedCode = (getNamespace() != '');
+  
+                  fCheckedForVolunteersNamespace = true;
+              }
+              return IsManagedCode;
+          }
+          set;
+      }
+  
+      /******************************************************************************************************
+       * @description String helper property for getNamespace() method.
+       *******************************************************************************************************/
+      private static String plainNamespace;
+  
+      /*******************************************************************************************************
+       * @description Finds the namespace for the current context.
+       * @return  The current namespace as a , or a blank  if we're not in a namespaced context.
+       ********************************************************************************************************/
+      public static String getNamespace() {
+          if (plainNamespace == null) {
+              String withDotNotation = VOL_SharedCode.class.getName();
+  
+              if (withDotNotation.contains('.')) {
+                  plainNamespace = withDotNotation.substringBefore('.');
+              } else {
+                  plainNamespace = '';
+              }
+          }
+          return plainNamespace;
+      }
+  
+      /*******************************************************************************************************
+       * @description Static method that takes a string
+       * If we are in a managed package, tokens in dynamic SOQL must include the package namespace prefix.
+       * If you ever deploy this package as unmanaged, this routine will do nothing!
+       * @param str token name
+       * @return token name, with namespace prefix, if required.
+       ********************************************************************************************************/
+      global static String strTokenNSPrefix(String str) {
+          if (getNamespace() == '') {
+              return str;
+          }
+          str = getNamespace() + '__' + str;
+          return str;
+      }
+  
+      // utility to verify all the specified fields are accessible to the current user.
+      // fields that are not accessible will have a pageMessage added to the current page
+      // so the warning is displayed to the user.
+      global static void testObjectFieldVisibility(String strObj, List<String> listStrField) {
+          Map<String, Schema.SObjectType> gd;
+          Schema.DescribeSObjectResult sobjDescr;
+          Map<String, Schema.SObjectField> mapFieldDesc;
+  
+          // Obtaining the field name/token map for the object
+          gd = Schema.getGlobalDescribe();
+          if (gd != null) {
+              sobjDescr = gd.get(strObj).getDescribe();
+          }
+          if (sobjDescr != null) {
+              mapFieldDesc = sobjDescr.fields.getMap();
+          }
+          if (mapFieldDesc != null) {
+              for (String strField : listStrField) {
+                  // Check if the user has access on the each field
+                  // note that fields in our own package must not have their prefix for the Describe Field Map
+                  Schema.SObjectField fld = mapFieldDesc.get(strField.replace(StrTokenNSPrefix(''), ''));
+                  if (fld != null && !fld.getDescribe().isAccessible()) {
+                      ApexPages.addMessage(
+                          new ApexPages.Message(
+                              ApexPages.Severity.FATAL,
+                              // prettier-ignore
+                              'Field ' + strObj + '.' + strField + ' You need to enable field level security for this field on the Site\'s Guest User profile.'
+                          )
+                      );
+                  }
+              }
+          }
+      }
+  
+      /*******************************************************************************************************
+       * @description Static method checks if running user has field update access for a set of fields
+       * @param objectName the name of the object the field belongs to
+       * @param fieldNames the set of field names to check update access
+       * @return void
+       ********************************************************************************************************/
+      public static void checkUpdateAccessSites(String objectName, Set<String> fieldNames) {
+          // for backward compatibility with 1000's of nonprofit customers, we can
+          // only enforce create permissions on the Sites user for Contacts.
+          if (objectName == 'Contact') {
+              access.checkCreateAccess(objectName, fieldNames);
+          } else {
+              access.checkUpdateAccess(objectName, fieldNames);
+          }
+      }
+  
+      /**
+       * @description DML options to allow overriding duplicate rules to create contacts, while throwing
+       * exceptions for validation rules, required fields, etc.
+       */
+      private static Database.DMLOptions dmlDuplicateOptions {
+          get {
+              if (dmlDuplicateOptions == null) {
+                  dmlDuplicateOptions = new Database.DMLOptions();
+                  dmlDuplicateOptions.optAllOrNone = true;
+                  dmlDuplicateOptions.DuplicateRuleHeader.allowSave = true;
+              }
+              return dmlDuplicateOptions;
+          }
+          set;
+      }
+  
+      // This massively nested SOQL where statement looks hard coded and dumb, but as it turns out
+      // there's a limit on number of campaign hierarchy levels anyway, so this isn't as dumb as it
+      // seems. This method will get all campaigns in hierarchy, and keeps the logic in a single query
+  
+      /*******************************************************************************************************
+       * @description Static method that takes an Id
+       * Return a list of Campaign Ids that are children/grand-children &c of the given Campaign.
+       * @param Id for any campaign
+       * @return List<Id> of child campaigns
+       ********************************************************************************************************/
+      global static List<Id> listIdsCampaignsInHierarchy(Id campaignId) {
+          Map<Id, Campaign> campaignsInHierarchy = new Map<Id, Campaign>(
+              [
+                  SELECT Id, Name
+                  FROM Campaign
+                  WHERE
+                      IsActive = :true
+                      AND RecordTypeId = :recordtypeIdVolunteersCampaign
+                      AND (Id = :campaignId
+                      OR ParentId = :campaignId
+                      OR Parent.ParentId = :campaignId
+                      OR Parent.Parent.ParentId = :campaignId
+                      OR Parent.Parent.Parent.ParentId = :campaignId
+                      OR Parent.Parent.Parent.Parent.ParentId = :campaignId)
+              ]
+          );
+          return new List<Id>(campaignsInHierarchy.keySet());
+      }
+  
+      /*******************************************************************************************************
+       * @description keeps references to old labels that have been packaged, that we no longer use.
+       ********************************************************************************************************/
+      private static void keepUnusedLabelsInPackage() {
+          String str;
+          str = Label.labelCalendarConfirmed;
+          str = Label.labelContactInfoRankText;
+          str = Label.labelHide;
+          str = Label.labelMassEmailHelp1;
+          str = Label.labelMassEmailHelp2;
+          str = Label.labelMassEmailHelp3;
+          str = Label.labelMassEmailHelp4;
+          str = Label.labelMassEmailHelp5;
+          str = Label.labelMassEmailHelp6;
+          str = Label.labelMassEmailHelp7;
+          str = Label.labelShowHelp;
+          str = Label.labelFindVolunteersCriteria;
+          str = Label.labelFindVolunteersHelpAssign;
+          str = Label.labelVolunteersWizardNewCampaignTitle;
+          str = Label.labelContactLookupSuccess;
+          str = Label.labelContactLookupNotFound;
+      }
+  }    
+
+ + diff --git a/demo/testcode.css b/demo/testcode.css new file mode 100644 index 0000000..122de5e --- /dev/null +++ b/demo/testcode.css @@ -0,0 +1,163 @@ +pre code.hljs { + display: block; + overflow-x: auto; + padding: 1em + } + code.hljs { + padding: 3px 5px + } + /*! + Theme: Google Light + Author: Seth Wright (http://sethawright.com) + License: ~ MIT (or more permissive) [via base16-schemes-source] + Maintainer: @highlightjs/core-team + Version: 2021.09.0 + */ + /* + WARNING: DO NOT EDIT THIS FILE DIRECTLY. + + This theme file was auto-generated from the Base16 scheme google-light + by the Highlight.js Base16 template builder. + + - https://github.com/highlightjs/base16-highlightjs + */ + /* + base00 #ffffff Default Background + base01 #e0e0e0 Lighter Background (Used for status bars, line number and folding marks) + base02 #c5c8c6 Selection Background + base03 #b4b7b4 Comments, Invisibles, Line Highlighting + base04 #969896 Dark Foreground (Used for status bars) + base05 #373b41 Default Foreground, Caret, Delimiters, Operators + base06 #282a2e Light Foreground (Not often used) + base07 #1d1f21 Light Background (Not often used) + base08 #CC342B Variables, XML Tags, Markup Link Text, Markup Lists, Diff Deleted + base09 #F96A38 Integers, Boolean, Constants, XML Attributes, Markup Link Url + base0A #FBA922 Classes, Markup Bold, Search Text Background + base0B #198844 Strings, Inherited Class, Markup Code, Diff Inserted + base0C #3971ED Support, Regular Expressions, Escape Characters, Markup Quotes + base0D #3971ED Functions, Methods, Attribute IDs, Headings + base0E #A36AC7 Keywords, Storage, Selector, Markup Italic, Diff Changed + base0F #3971ED Deprecated, Opening/Closing Embedded Language Tags, e.g. + */ + pre code.hljs { + display: block; + overflow-x: auto; + padding: 1em + } + code.hljs { + padding: 3px 5px + } + .hljs { + color: #373b41; + background: #ffffff + } + .hljs::selection, + .hljs ::selection { + background-color: #c5c8c6; + color: #373b41 + } + /* purposely do not highlight these things */ + .hljs-formula, + .hljs-params, + .hljs-property { + + } + /* base03 - #b4b7b4 - Comments, Invisibles, Line Highlighting */ + .hljs-comment { + color: #b4b7b4 + } + /* base04 - #969896 - Dark Foreground (Used for status bars) */ + .hljs-tag { + color: #969896 + } + /* base05 - #373b41 - Default Foreground, Caret, Delimiters, Operators */ + .hljs-subst, + .hljs-punctuation, + .hljs-operator { + color: #373b41 + } + .hljs-operator { + opacity: 0.7 + } + /* base08 - Variables, XML Tags, Markup Link Text, Markup Lists, Diff Deleted */ + .hljs-bullet, + .hljs-variable, + .hljs-template-variable, + .hljs-selector-tag, + .hljs-name, + .hljs-deletion { + color: #CC342B + } + /* base09 - Integers, Boolean, Constants, XML Attributes, Markup Link Url */ + .hljs-symbol, + .hljs-number, + .hljs-link, + .hljs-attr, + .hljs-variable.constant_, + .hljs-literal { + color: #F96A38 + } + /* base0A - Classes, Markup Bold, Search Text Background */ + .hljs-title, + .hljs-class .hljs-title, + .hljs-title.class_ { + color: #FBA922 + } + .hljs-strong { + font-weight: bold; + color: #FBA922 + } + /* base0B - Strings, Inherited Class, Markup Code, Diff Inserted */ + .hljs-code, + .hljs-addition, + .hljs-title.class_.inherited__, + .hljs-string { + color: #198844 + } + /* base0C - Support, Regular Expressions, Escape Characters, Markup Quotes */ + /* guessing */ + .hljs-built_in, + .hljs-doctag, + .hljs-quote, + .hljs-keyword.hljs-atrule, + .hljs-regexp { + color: #3971ED + } + /* base0D - Functions, Methods, Attribute IDs, Headings */ + .hljs-function .hljs-title, + .hljs-attribute, + .ruby .hljs-property, + .hljs-title.function_, + .hljs-section { + color: #3971ED + } + /* base0E - Keywords, Storage, Selector, Markup Italic, Diff Changed */ + /* .hljs-selector-id, */ + /* .hljs-selector-class, */ + /* .hljs-selector-attr, */ + /* .hljs-selector-pseudo, */ + .hljs-type, + .hljs-template-tag, + .diff .hljs-meta, + .hljs-keyword { + color: #A36AC7 + } + .hljs-emphasis { + color: #A36AC7; + font-style: italic + } + /* base0F - Deprecated, Opening/Closing Embedded Language Tags, e.g. */ + /* + prevent top level .keyword and .string scopes + from leaking into meta by accident + */ + .hljs-meta, + .hljs-meta .hljs-keyword, + .hljs-meta .hljs-string { + color: #3971ED + } + /* for v10 compatible themes */ + .hljs-meta .hljs-keyword, + .hljs-meta-keyword { + font-weight: bold + } \ No newline at end of file diff --git a/demo/testcode.html b/demo/testcode.html index f07bcaf..c09833e 100644 --- a/demo/testcode.html +++ b/demo/testcode.html @@ -5,9 +5,8 @@ - + @@ -20,6 +19,20 @@ +

+      List<SObject> mylist = [SELECT Name, StreetAddress__c, COUNT(),
+      FROM Warehouse__c 
+      WHERE DISTANCE(Location__c, GEOLOCATION(37.775,-122.418), 'mi') < 20 
+      AND Date__kav NOT IN LAST_N_DAYS:80
+      ORDER BY DISTANCE(Location__c, GEOLOCATION(37.775,-122.418), 'mi')
+      LIMIT 10];
+    
+

+      @IsTest
+      private without sharing class myTestClass {
+        
+      }
+    

     /*
     Copyright (c) 2022, salesforce.com, inc.
@@ -543,7 +556,7 @@
     

-public without sharing class AccountSampleTriggerHandler extends TriggerHandler, Database.schedulable, Batchable<SObject> {
+public without sharing class AccountSampleTriggerHandler extends Database.schedulable, TriggerHandler, Database.Batchable<SObject> {
   private List<Account> newRecords;
   private List<Account> oldRecords;
   private Map<Id, Account> newRecordsMap;
@@ -553,21 +566,6 @@
     when 'BEFORE_INSERT' {
       this.triggerEvent = System.TriggerOperation.BEFORE_INSERT;
     }
-    when 'BEFORE_UPDATE' {
-      this.triggerEvent = System.TriggerOperation.BEFORE_UPDATE;
-    }
-    when 'BEFORE_DELETE' {
-      this.triggerEvent = System.TriggerOperation.BEFORE_DELETE;
-    }
-    when 'AFTER_INSERT' {
-      this.triggerEvent = System.TriggerOperation.AFTER_INSERT;
-    }
-    when 'AFTER_UPDATE' {
-      this.triggerEvent = System.TriggerOperation.AFTER_UPDATE;
-    }
-    when 'AFTER_DELETE' {
-      this.triggerEvent = System.TriggerOperation.AFTER_DELETE;
-    }
     when 'AFTER_UNDELETE' {
       this.triggerEvent = System.TriggerOperation.AFTER_UNDELETE;
     }
@@ -643,7 +641,6 @@
   @InvocableMethod(label='my invocable')
   public void moveTo(
     integer x, 
-    integer y, 
     integer z
   ) {
     
@@ -653,7 +650,7 @@
     Boolean ai = (Boolean) false;
     System.debug('Should not be called');
     if (1 > 5) { // wtf!?
-      Database.insert(myAccounts);
+      Database.insert(myAccounts, AccessLevel.USER_MODE);
     }
   }
 
diff --git a/demo/vs.css b/demo/vs.css
new file mode 100644
index 0000000..a1ed6da
--- /dev/null
+++ b/demo/vs.css
@@ -0,0 +1,65 @@
+/*
+
+Visual Studio-like style based on original C# coloring by Jason Diamond 
+
+*/
+.hljs {
+  background: white;
+  color: black;
+}
+
+.hljs-comment,
+.hljs-quote,
+.hljs-variable {
+  color: #008000;
+}
+
+.hljs-keyword,
+.hljs-selector-tag,
+.hljs-built_in,
+.hljs-name,
+.hljs-tag {
+  color: #00f;
+}
+
+.hljs-string,
+.hljs-title,
+.hljs-section,
+.hljs-attribute,
+.hljs-literal,
+.hljs-template-tag,
+.hljs-template-variable,
+.hljs-type,
+.hljs-addition {
+  color: #a31515;
+}
+
+.hljs-deletion,
+.hljs-selector-attr,
+.hljs-selector-pseudo,
+.hljs-meta {
+  color: #2b91af;
+}
+
+.hljs-doctag {
+  color: #808080;
+}
+
+.hljs-attr {
+  color: #f00;
+}
+
+.hljs-symbol,
+.hljs-bullet,
+.hljs-link {
+  color: #00b0e8;
+}
+
+
+.hljs-emphasis {
+  font-style: italic;
+}
+
+.hljs-strong {
+  font-weight: bold;
+}
diff --git a/dist/apex.es.min.js b/dist/apex.es.min.js
index d3300c0..94aad72 100644
--- a/dist/apex.es.min.js
+++ b/dist/apex.es.min.js
@@ -1,69 +1,107 @@
-/*! `apex` grammar compiled for Highlight.js 11.8.0 */
+/*! `apex` grammar compiled for Highlight.js 11.9.0 */
 var hljsGrammar=(()=>{"use strict";return e=>{
 const t=e.regex,a="[a-zA-Z][a-zA-Z_0-9]*",s={scope:"number",variants:[{
-match:/\b[0-9]+(?:\.[0-9]+)?/},{match:/\s(?:[0-9,]+)?\.[0-9]+/},{
-match:/\b0(x|X)[0-9a-fA-F_]+(U|u|L|l|UL|Ul|uL|ul|LU|Lu|lU|lu)?\b/},{
-match:/\b0(b|B)[01_]+(U|u|L|l|UL|Ul|uL|ul|LU|Lu|lU|lu)?\b/},{
-match:/\b([0-9_]+)?\.[0-9_]+((e|E)[0-9]+)?(F|f|D|d|M|m)?\b/},{
-match:/\b[0-9_]+(e|E)[0-9_]+(F|f|D|d|M|m)?\b/},{match:/\b[0-9_]+(F|f|D|d|M|m)\b/
-},{match:/\b[0-9_]+(U|u|L|l|UL|Ul|uL|ul|LU|Lu|lU|lu)?\b/}],relevance:0},c={
-$pattern:"[A-Za-z][0-9A-Za-z$_]*",
-keyword:["trigger|10","class","interface","abstract","AccessLevel","USER_MODE","SYSTEM_MODE","AccessType","break","cast","catch","continue","default","do","else","exports","extends|6","finally","for","get","put","set","global","if","implements","new","newMap|10","old|10","oldMap|10","operationType","override","private","protected","public","return","size","static","throws","throw","testmethod|10","try","virtual","webservice","when","while"],
-"variable.language":["final","instanceof","super","this","transient"],
-built_in:["finish","start","execute"].concat(["insert","update","upsert|8","delete","undelete","merge","convertLead|10"]),
-type:["anytype","blob|0","boolean|0","byte|0","currency|0","date|0","datetime|0","decimal|0","double|0","enum|0","float|0","integer|0","long|0","object","pagereference|10","selectoption|10","short|0","sobject|10","string|0","time|0","void|0","float|0"],
-literal:["false","true","null"]},n={
+match:/(-?)\b[0-9]+(?:\.[0-9]+)?/},{
+match:/(-?)\b([0-9_]+)?\.[0-9_]+((e|E)[0-9]+)?(F|f|D|d|M|m)?\b/},{
+match:/(-?)\b[0-9_]+(e|E)[0-9_]+(F|f|D|d|M|m)?\b/},{
+match:/(-?)\b[0-9_]+(F|f|D|d|M|m)\b/},{
+match:/\b[0-9_]+(U|u|L|l|UL|Ul|uL|ul|LU|Lu|lU|lu)?\b/}],relevance:0
+},c=["false","true","null"],n=["ApexPages|10","AppLauncher","Approval","Auth","Cache","Canvas","ChatterAnswers|10","CommercePayments|10","ConnectApi|10","Database","Datacloud|10","Dataweave|10","DataSource|10","Dom","EventBus|10","ExternalService","Flow","Functions","Invocable","KbManagement|10","Label","LxScheduler|10","Messaging","Metadata","Pref_center|10","Process","QuickAction","Reports","RichMessageing","Savepoint","SchedulableContext","Schema","Search","Sfc|10","Sfdc_Checkout|10","sfdc_surveys|10","Site","Support","System","TerritoryMgmt|10","Test","Trigger|10","TxnSecurity|10","Type","UserProvisioning|10","VisualEditor|10","Wave|10"],r=[{
+match:[/\b/,t.either("AccessLevel","Address","Answers","ApexPages","Approval","Assert","AsyncInfo","AsyncOptions","BusinessHours","Cases","Collator","Continuation","Cookie","Crypto","Database","Date","Datetime","Decimal","Domain","DomainCreator","DomainParser","EmailMessages","EncodingUtil","EventBus","Exception","FeatureManagement","FlexQueue","Formula","FormulaRecalcFieldError","FormulaRecalcResult","Http","HttpRequest","HttpResponse","Ideas","JSON","JSONGenerator","JSONParser","Label","Limits","Location","Matcher","Math","Messaging","MultiStaticResourceCalloutMock","Network","OrgLimit","OrgLimits","Packaging","PageReference","Pattern","QueueableDuplicateSignature","QueueableDuplicateSignature.Builder","QuickAction","Request","ResetPasswordResult","RestContext","RestRequest","RestResponse","Schema","Search","Security","SelectOption","Site","SObject","SObjectAccessDecision","StaticResourceCalloutMock","TimeZone","Type","URL","UserInfo","UserManagement","Version","WebServiceCallout","XmlStreamReader","XmlStreamWriter"),/\./],
+scope:{2:"title"},relevance:10}],i=[{
+match:[/\b/,t.either("Callable","Comparable","Comparator","HttpCalloutMock","InstallHandler","Queueable","QueueableContext","SandboxPostCopy","Schedulable","SchedulableContext","StubProvider","UninstallHandler","WebServiceMock"),/\b/],
+scope:{2:"title.class.inherited"},relevance:10},{
+match:[/\b/,t.either(...n),/\./,a,/\b/],scope:{2:"built_in",4:"title.class"}},{
+match:[/\b/,t.either(...n),/(?=\.)/],scope:{2:"built_in"},relevance:10},{
+match:[/\bSystem/,".",a,/(?=\.)/],scope:{1:"built_in",3:"title.class"},
+relevance:10},{
+match:[/\b/,t.either("AccessType","DomainType","JSONToken","LoggingLevel","Quiddity","TriggerOperation"),/\./,a,/\b/],
+scope:{2:"built_in",4:"type"}}],o={
 match:t.either(/-/,/--/,/~/,/\*/,/\*=/,/\/=/,/%/,/\+/,/\+\+/,/<>/,/>=/,/<=/,/\s<\s/,/\s>\s/,/\^/,/\^=/,/!=/,/!/,/==/,/&&/,/&/,/\|\|/,/\|/,/(?<=\s)\?|:(?=\s)/,/=/,/=>/,/\?\./),
-scope:"operator",relevance:0},r={
-match:[/\b/,t.either("ApexPages|10","AppLauncher","Approval","Assert","Auth","Cache","Canvas","ChatterAnswers|10","CommercePayments|10","ConnectApi|10","Database","Datacloud|10","Dataweave|10","DataSource|10","Dom","EventBus|10","ExternalService","Flow","Functions","Invocable","KbManagement|10","Label","LxScheduler|10","Messaging","Metadata","Pref_center|10","Process","QuickAction","Reports","RichMessageing","Savepoint","SchedulableContext","Schema","Search","Sfc|10","Sfdc_Checkout|10","sfdc_surveys|10","Site","Support","System","TerritoryMgmt|10","Test","Trigger|10","TxnSecurity|10","Type","UserProvisioning|10","VisualEditor|10","Wave|10"),/(?=\.)/],
-scope:{2:"built_in"},relevance:10
-},o=e.COMMENT("//",/[$\n]/),i=e.COMMENT("/\\*","\\*/",{relevance:0,contains:[{
+scope:"operator",relevance:0},l={match:t.either("{","}",",","<",">",/\./),
+scope:"punctuation",relevance:0
+},b=e.COMMENT("//",/[$\n]/),p=e.COMMENT("/\\*","\\*/",{relevance:0,contains:[{
 begin:/\w+@/,relevance:0},{scope:"doctag",begin:"@[A-Za-z_]+"},{begin:"`",
 end:"`",excludeBegin:!0,excludeEnd:!0,scope:"code",
 contains:[e.BACKSLASH_ESCAPE],relevance:0},e.APOS_STRING_MODE,{
-match:[/(?<=@param)/,/\s+/,/\w+/],scope:{3:"variable"}}]})
-;t.either("label","description","callout","required","category","configurationEditor","iconName","SeeAllData")
-;const l={relevance:10,scope:{1:"meta"},match:["@"+a]},b=[{
-match:/\b[a-zA-Z\d]*Exception\b/,scope:"title.class",relevance:0},{
-match:[/\wthrow\s+new\s+/,a],scope:{1:"keyword",2:"title.class"},relevance:0
-}],p=[{match:[t.concat(/\b/,a,/\b/),/>/],scope:{1:"type"},relevance:10}],u=[{
+match:[/(?<=@param)/,/\s+/,/\w+/],scope:{3:"variable"}}]}),u={
+match:[/(?"],scope:{1:"type"},relevance:10}],S=[{
 match:[/\b(list|set|map)\s*/,"<",/[\.\w]+/],scope:{1:"type",3:"type"},
-relevance:10},{match:[a,t.lookahead(/\s*\[\]/)],scope:{1:"type"}}],E={
-variants:[{match:[/\./,t.concat("(?:"+a+")"),/(?=\s*\(\))/],scope:{
+relevance:10},{match:[a,t.lookahead(/\s*\[\]/)],scope:{1:"type"}}],d={
+match:[/(\w|[^\.])/,t.concat(/\b/,a,/__(c|pc|r|b|e|mdt|x|share|kav|ka|history|del|s)/,/\b/),/(?=[\(\s;,])/],
+scope:{2:"type"},relevance:10},E={contains:[s,e.APOS_STRING_MODE],illegal:":",
+relevance:0,variants:[{match:t.concat(/\b/,t.either(...c),/\b/),scope:"literal"
+},{match:[a,/\./,a,/\s*(?=[,)])/],scope:{1:"variable",3:"property"}},{
+match:[a,/\s+/,a,/\s*(?=[,)])/],scope:{1:"type",3:"variable"}},{
+match:[t.either(a,t.concat(a,/\./,a)),/\s+(?![,)])/],scope:{1:"variable"}},{
+match:[a,/\s*(?=[,)])/],scope:{1:"variable"}}]},v=[{
+begin:[/\bnew\b/,/\s+/,a,/\s*/,/\(/],beginScope:{1:"keyword",
+3:"title.function.invoke"},end:/(?=\))/,returnEnd:!0,scope:"params",
+contains:[E,{match:/\(\)/},b],illegal:":"}],R={variants:[{
+begin:[/\./,t.concat("(?:"+a+")"),/(?=\s*\(\))/],beginScope:{
 2:"title.function.invoke"}},{
-match:[/\./,t.concat("(?:"+a+")"),/(?=\s*\([^\)])/],scope:{
-2:"title.function.invoke"}},{
-match:[/(?<=\s)/,t.concat("(?:"+a+")"),/(?=\s*\()/],scope:{2:"title.function"}
-}],contains:[o,i,e.APOS_STRING_MODE],relevance:0},m={
-begin:/\[[\s\n]*(?=(SELECT|FIND))/,end:/\]/,scope:"subst",relevance:10,
-contains:[{
-begin:t.concat(/\b/,t.either("ABOVE_OR_BELOW","ABOVE","ACTIVE","ADVANCED","ALL",/ALL\s+FIELDS/,"AND","ANY","ARRAY","AS","ASC","BY","CATEGORY","CONTAINS","COUNT","COUNT_DISTINCT","SUM","MAX","MIN","HOUR_IN_DAY","CONVERTCURRENCY","CUBE","DATA","DESC","DIVISION","END","EXCLUDES","FIELDS","FIND|10","FIRST","FOR","FROM",/GROUP\s+BY/,"HAVING","INCLUDES","LAST","LAST_90_DAYS","LAST_MONTH","LAST_N_DAYS","LAST_WEEK","LAST","LIKE","LIMIT","NETWORK","NEXT_90_DAYS","NEXT_MONTH","NEXT_N_DAYS","NEXT_WEEK","NULLS","OFFSET","ON","OR",/ORDER\s+BY/,"REFERENCE","RETURNING","ROLLUP","ROWS","SEARCH","SECURITY_ENFORCED","SELECT","SNIPPET","SORT","THIS_MONTH","THIS_WEEK","TODAY","TOLABEL","TOMORROW","TRACKING","TYPEOF","UPDATE",/USING\s+SCOPE/,"VIEW","VIEWSTAT","VIEWSTATE","WHERE","WITH","YESTERDAY","USER_MODE"),/\b/),
-scope:"keyword"},{match:/(\bIN\b|<|<=|>|>=|\bNOT\s+IN\b|=|!\s*=|\s:{1}|:{1}\s)/,
+begin:[/(?<=\s)/,t.concat("(?:"+a+")"),/(?=\s*\()/],beginScope:{
+2:"title.function"}}],end:/(?=\))/,returnEnd:!0,
+contains:[b,p,e.APOS_STRING_MODE,E,v],relevance:0},g=[{
+begin:[/\b(?<=enum)\s+/,a,/\s*/,/[{()]/],beginScope:{2:"type",4:"punctuation"},
+end:/[})]/,endScope:"punctuation",relevance:0,contains:[b,p,{
+match:t.concat(/\b/,a,/\b/),scope:"variable.constant"}]},{
+match:[/(?<=\bclass\b)/,/\s+/,a],scope:{1:"keyword",3:"title.class"}},{
+begin:[/(?<=(public|private))/,/\s+/,a,/(?=\s*\()/],beginScope:{3:"constructor"
+},end:/\)\s*{/,contains:[E]},{
+begin:[/(?<=\btrigger\b)/,/\s+/,a,/\s+/,"on",/\s+/,a],end:"{",scope:{
+1:"keyword",3:"title.class",5:"operator",7:"type"},contains:[b,p,{
+match:/(?:before|after)\s+(?:insert|update|delete|undelete)/,scope:"built_in",
+relevance:10}],relevance:10},{begin:/(?<=extends|implements)\s*/,end:"{",
+contains:[i,{match:[/\b/,a,/\b/],scope:{2:"title.class.inherited"}},{
+match:[/\b/,a,/\./,a,/\b/],scope:{2:"title.class.inherited",
+4:"title.class.inherited"}}]}],T=[{match:[a,/\s+/,a,/\s+/,/=/],scope:{1:"type",
+3:"variable",5:"operator"},relevance:0},{match:[a,/\s+/,a,/\s+/,";"],scope:{
+1:"type",3:"variable"},relevance:0},{match:[/\s+/,a,/\s+/,/=/],scope:{
+2:"variable",4:"operator"},relevance:0}],O=[{
+match:[/(?<=return)/,/\s+/,a,/(?=\s*;)/],scope:{3:"variable"}},{
+begin:[/(?<=return)/,/\s+/,a,/\(/],end:/\)/,beginScope:{
+3:"title.function.invoke"},contains:[E]}],N={begin:/\[\s*\b(SELECT|FIND)\b/,
+end:/\]/,scope:"soql",relevance:10,contains:[{
+begin:t.concat(/\b/,t.either("ABOVE_OR_BELOW","ACTIVE","ADVANCED","ALL","ANY","ARRAY","AS","BY","CATEGORY","CONTAINS","CUSTOM","DATA","DIVISION","END","FIELDS","FIND|10","LAST","METADATA","NETWORK","ON","RETURNING","ROLLUP","ROWS","SEARCH","SECURITY_ENFORCED","SELECT","SNIPPET","SORT","STANDARD","USER_MODE","WHERE"),/\b/),
+scope:"keyword"},{
+match:t.concat(/\b/,t.either(":","ABOVE","AND","ASC","AT","DESC","DISTANCE","FROM","HAVING","IN","LIKE","LIMIT","LISTVIEW","NOT","OFFSET","OR","TRACKING","TYPEOF","UPDATE","USING","VIEWSTAT","YESTERDAY",/FOR\s+REFERENCE/,/FOR\s+UPDATE/,/FOR\s+VIEW/,/GROUP\s+BY/,/NOT\s+IN/,/NULLS\s+FIRST/,/NULLS\s+LAST/,/ORDER\s+BY/,/WITH\s+DATA\s+CATEGORY/,/WITH\s+PricebookId/),/\b/),
+scope:"operator",relevance:0},{
+match:t.concat(/\b/,t.either("AVG","convertCurrency","convertTimezone","COUNT","COUNT_DISTINCT","DAY_IN_MONTH","DAY_IN_WEEK","DAY_IN_YEAR","DAY_ONLY","toLabel","INCLUDES","EXCLUDES","FORMAT","GROUPING","GROUP BY CUBE","GROUP BY ROLLUP","HOUR_IN_DAY","MAX","MIN","SUM","WEEK_IN_MONTH","WEEK_IN_YEAR"),/\b/),
+scope:"title.function.invoke"},{
+match:/(NEXT|LAST|THIS)_(N_)?(90_DAY|DAY|FISCAL_QUARTER|FISCAL_YEAR|MONTH|QUARTER|WEEK|YEAR)S?/,
+scope:"literal"},{
+match:t.concat(/\b/,t.either("CALENDAR_MONTH","CALENDAR_QUARTER","CALENDAR_YEAR","FISCAL_MONTH","FISCAL_QUARTER","FISCAL_YEAR","TODAY","TOMORROW"),/\b/),
 scope:"literal"},{match:/(?<=\bFROM\b\s+)\w+/,scope:"type",relevance:0},{
-match:[t.concat(/\b/,"[a-zA-Z][a-zA-Z_]*"),":",/[0-9]+\b/],scope:{1:"keyword",
-3:"number"},relevance:10},s,E,e.APOS_STRING_MODE],illegal:"::"};return{
+match:[/\bUSING\s+SCOPE\b\s+/,t.either("Delegated","Everything","Mine","My_Territory","My_Team_Territory","Team")],
+scope:{1:"variable.language",2:"variable.language"}},{
+match:[/(NEXT|LAST|THIS)_(N_)?(90_DAY|DAY|FISCAL_QUARTER|FISCAL_YEAR|MONTH|QUARTER|WEEK|YEAR)S?/,/\s*/,":",/\s*/,/[0-9]+\b/],
+scope:{1:"variable.language",3:"punctuation",5:"number"},relevance:10},{
+match:[/(?<=:)/,/\s*/,a],scope:{3:"variable"}},{match:/[(:)]/,
+scope:"punctuation",relevance:0},s,R,e.APOS_STRING_MODE,o],illegal:"::"},_={
+variants:[{match:[/\bfor\b/,/\s*\(/,a,/\s+/,a,/\s*:/,/(?=\s*\[)/],scope:{
+1:"keyword",3:"type",5:"variable",6:"punctuation"}},{
+match:[/\bfor\b/,/\s*\(/,a,/\s+/,a,/\s*:/,/\s*/,a],scope:{1:"keyword",
+2:"punctuation",3:"type",5:"variable",8:"variable"}}],contains:[b,p,N]};return{
 name:"Apex",aliases:["apex","lightning"],case_insensitive:!0,
-disableAutodetect:!1,ignoreIllegals:!1,keywords:c,
-illegal:["","\x3c!--","!DOCTYPE",/","\x3c!--","!DOCTYPE",//],
+contains:[m,g,T,{relevance:10,match:/\b(with|without|inherited)\s+sharing\b/,
+scope:"keyword"},S,h,p,b,{match:/\b(switch\s+on|as\s+user|as\s+system)\b/,
+relevance:8,scope:"keyword"},d,{
+match:["AccessLevel",".",/(SYSTEM_MODE|USER_MODE)/],scope:{1:"built_in",
+3:"keyword"}},A,_,e.APOS_STRING_MODE,v,R,i,s,o,l,O,{match:/(?{var e=(()=>{"use strict";return e=>{
-const t=e.regex,s="[a-zA-Z][a-zA-Z_0-9]*",a={scope:"number",variants:[{
-match:/\b[0-9]+(?:\.[0-9]+)?/},{match:/\s(?:[0-9,]+)?\.[0-9]+/},{
-match:/\b0(x|X)[0-9a-fA-F_]+(U|u|L|l|UL|Ul|uL|ul|LU|Lu|lU|lu)?\b/},{
-match:/\b0(b|B)[01_]+(U|u|L|l|UL|Ul|uL|ul|LU|Lu|lU|lu)?\b/},{
-match:/\b([0-9_]+)?\.[0-9_]+((e|E)[0-9]+)?(F|f|D|d|M|m)?\b/},{
-match:/\b[0-9_]+(e|E)[0-9_]+(F|f|D|d|M|m)?\b/},{match:/\b[0-9_]+(F|f|D|d|M|m)\b/
-},{match:/\b[0-9_]+(U|u|L|l|UL|Ul|uL|ul|LU|Lu|lU|lu)?\b/}],relevance:0},c={
-$pattern:"[A-Za-z][0-9A-Za-z$_]*",
-keyword:["trigger|10","class","interface","abstract","AccessLevel","USER_MODE","SYSTEM_MODE","AccessType","break","cast","catch","continue","default","do","else","exports","extends|6","finally","for","get","put","set","global","if","implements","new","newMap|10","old|10","oldMap|10","operationType","override","private","protected","public","return","size","static","throws","throw","testmethod|10","try","virtual","webservice","when","while"],
-"variable.language":["final","instanceof","super","this","transient"],
-built_in:["finish","start","execute"].concat(["insert","update","upsert|8","delete","undelete","merge","convertLead|10"]),
-type:["anytype","blob|0","boolean|0","byte|0","currency|0","date|0","datetime|0","decimal|0","double|0","enum|0","float|0","integer|0","long|0","object","pagereference|10","selectoption|10","short|0","sobject|10","string|0","time|0","void|0","float|0"],
-literal:["false","true","null"]},n={
+const t=e.regex,a="[a-zA-Z][a-zA-Z_0-9]*",s={scope:"number",variants:[{
+match:/(-?)\b[0-9]+(?:\.[0-9]+)?/},{
+match:/(-?)\b([0-9_]+)?\.[0-9_]+((e|E)[0-9]+)?(F|f|D|d|M|m)?\b/},{
+match:/(-?)\b[0-9_]+(e|E)[0-9_]+(F|f|D|d|M|m)?\b/},{
+match:/(-?)\b[0-9_]+(F|f|D|d|M|m)\b/},{
+match:/\b[0-9_]+(U|u|L|l|UL|Ul|uL|ul|LU|Lu|lU|lu)?\b/}],relevance:0
+},c=["false","true","null"],n=["ApexPages|10","AppLauncher","Approval","Auth","Cache","Canvas","ChatterAnswers|10","CommercePayments|10","ConnectApi|10","Database","Datacloud|10","Dataweave|10","DataSource|10","Dom","EventBus|10","ExternalService","Flow","Functions","Invocable","KbManagement|10","Label","LxScheduler|10","Messaging","Metadata","Pref_center|10","Process","QuickAction","Reports","RichMessageing","Savepoint","SchedulableContext","Schema","Search","Sfc|10","Sfdc_Checkout|10","sfdc_surveys|10","Site","Support","System","TerritoryMgmt|10","Test","Trigger|10","TxnSecurity|10","Type","UserProvisioning|10","VisualEditor|10","Wave|10"],i=[{
+match:[/\b/,t.either("AccessLevel","Address","Answers","ApexPages","Approval","Assert","AsyncInfo","AsyncOptions","BusinessHours","Cases","Collator","Continuation","Cookie","Crypto","Database","Date","Datetime","Decimal","Domain","DomainCreator","DomainParser","EmailMessages","EncodingUtil","EventBus","Exception","FeatureManagement","FlexQueue","Formula","FormulaRecalcFieldError","FormulaRecalcResult","Http","HttpRequest","HttpResponse","Ideas","JSON","JSONGenerator","JSONParser","Label","Limits","Location","Matcher","Math","Messaging","MultiStaticResourceCalloutMock","Network","OrgLimit","OrgLimits","Packaging","PageReference","Pattern","QueueableDuplicateSignature","QueueableDuplicateSignature.Builder","QuickAction","Request","ResetPasswordResult","RestContext","RestRequest","RestResponse","Schema","Search","Security","SelectOption","Site","SObject","SObjectAccessDecision","StaticResourceCalloutMock","TimeZone","Type","URL","UserInfo","UserManagement","Version","WebServiceCallout","XmlStreamReader","XmlStreamWriter"),/\./],
+scope:{2:"title"},relevance:10}],r=[{
+match:[/\b/,t.either("Callable","Comparable","Comparator","HttpCalloutMock","InstallHandler","Queueable","QueueableContext","SandboxPostCopy","Schedulable","SchedulableContext","StubProvider","UninstallHandler","WebServiceMock"),/\b/],
+scope:{2:"title.class.inherited"},relevance:10},{
+match:[/\b/,t.either(...n),/\./,a,/\b/],scope:{2:"built_in",4:"title.class"}},{
+match:[/\b/,t.either(...n),/(?=\.)/],scope:{2:"built_in"},relevance:10},{
+match:[/\bSystem/,".",a,/(?=\.)/],scope:{1:"built_in",3:"title.class"},
+relevance:10},{
+match:[/\b/,t.either("AccessType","DomainType","JSONToken","LoggingLevel","Quiddity","TriggerOperation"),/\./,a,/\b/],
+scope:{2:"built_in",4:"type"}}],o={
 match:t.either(/-/,/--/,/~/,/\*/,/\*=/,/\/=/,/%/,/\+/,/\+\+/,/<>/,/>=/,/<=/,/\s<\s/,/\s>\s/,/\^/,/\^=/,/!=/,/!/,/==/,/&&/,/&/,/\|\|/,/\|/,/(?<=\s)\?|:(?=\s)/,/=/,/=>/,/\?\./),
-scope:"operator",relevance:0},r={
-match:[/\b/,t.either("ApexPages|10","AppLauncher","Approval","Assert","Auth","Cache","Canvas","ChatterAnswers|10","CommercePayments|10","ConnectApi|10","Database","Datacloud|10","Dataweave|10","DataSource|10","Dom","EventBus|10","ExternalService","Flow","Functions","Invocable","KbManagement|10","Label","LxScheduler|10","Messaging","Metadata","Pref_center|10","Process","QuickAction","Reports","RichMessageing","Savepoint","SchedulableContext","Schema","Search","Sfc|10","Sfdc_Checkout|10","sfdc_surveys|10","Site","Support","System","TerritoryMgmt|10","Test","Trigger|10","TxnSecurity|10","Type","UserProvisioning|10","VisualEditor|10","Wave|10"),/(?=\.)/],
-scope:{2:"built_in"},relevance:10
-},o=e.COMMENT("//",/[$\n]/),i=e.COMMENT("/\\*","\\*/",{relevance:0,contains:[{
+scope:"operator",relevance:0},l={match:t.either("{","}",",","<",">",/\./),
+scope:"punctuation",relevance:0
+},b=e.COMMENT("//",/[$\n]/),p=e.COMMENT("/\\*","\\*/",{relevance:0,contains:[{
 begin:/\w+@/,relevance:0},{scope:"doctag",begin:"@[A-Za-z_]+"},{begin:"`",
 end:"`",excludeBegin:!0,excludeEnd:!0,scope:"code",
 contains:[e.BACKSLASH_ESCAPE],relevance:0},e.APOS_STRING_MODE,{
-match:[/(?<=@param)/,/\s+/,/\w+/],scope:{3:"variable"}}]})
-;t.either("label","description","callout","required","category","configurationEditor","iconName","SeeAllData")
-;const l={relevance:10,scope:{1:"meta"},match:["@"+s]},b=[{
-match:/\b[a-zA-Z\d]*Exception\b/,scope:"title.class",relevance:0},{
-match:[/\wthrow\s+new\s+/,s],scope:{1:"keyword",2:"title.class"},relevance:0
-}],p=[{match:[t.concat(/\b/,s,/\b/),/>/],scope:{1:"type"},relevance:10}],u=[{
+match:[/(?<=@param)/,/\s+/,/\w+/],scope:{3:"variable"}}]}),u={
+match:[/(?"],scope:{1:"type"},relevance:10}],S=[{
 match:[/\b(list|set|map)\s*/,"<",/[\.\w]+/],scope:{1:"type",3:"type"},
-relevance:10},{match:[s,t.lookahead(/\s*\[\]/)],scope:{1:"type"}}],E={
-variants:[{match:[/\./,t.concat("(?:"+s+")"),/(?=\s*\(\))/],scope:{
+relevance:10},{match:[a,t.lookahead(/\s*\[\]/)],scope:{1:"type"}}],d={
+match:[/(\w|[^\.])/,t.concat(/\b/,a,/__(c|pc|r|b|e|mdt|x|share|kav|ka|history|del|s)/,/\b/),/(?=[\(\s;,])/],
+scope:{2:"type"},relevance:10},E={contains:[s,e.APOS_STRING_MODE],illegal:":",
+relevance:0,variants:[{match:t.concat(/\b/,t.either(...c),/\b/),scope:"literal"
+},{match:[a,/\./,a,/\s*(?=[,)])/],scope:{1:"variable",3:"property"}},{
+match:[a,/\s+/,a,/\s*(?=[,)])/],scope:{1:"type",3:"variable"}},{
+match:[t.either(a,t.concat(a,/\./,a)),/\s+(?![,)])/],scope:{1:"variable"}},{
+match:[a,/\s*(?=[,)])/],scope:{1:"variable"}}]},v=[{
+begin:[/\bnew\b/,/\s+/,a,/\s*/,/\(/],beginScope:{1:"keyword",
+3:"title.function.invoke"},end:/(?=\))/,returnEnd:!0,scope:"params",
+contains:[E,{match:/\(\)/},b],illegal:":"}],g={variants:[{
+begin:[/\./,t.concat("(?:"+a+")"),/(?=\s*\(\))/],beginScope:{
 2:"title.function.invoke"}},{
-match:[/\./,t.concat("(?:"+s+")"),/(?=\s*\([^\)])/],scope:{
-2:"title.function.invoke"}},{
-match:[/(?<=\s)/,t.concat("(?:"+s+")"),/(?=\s*\()/],scope:{2:"title.function"}
-}],contains:[o,i,e.APOS_STRING_MODE],relevance:0},m={
-begin:/\[[\s\n]*(?=(SELECT|FIND))/,end:/\]/,scope:"subst",relevance:10,
-contains:[{
-begin:t.concat(/\b/,t.either("ABOVE_OR_BELOW","ABOVE","ACTIVE","ADVANCED","ALL",/ALL\s+FIELDS/,"AND","ANY","ARRAY","AS","ASC","BY","CATEGORY","CONTAINS","COUNT","COUNT_DISTINCT","SUM","MAX","MIN","HOUR_IN_DAY","CONVERTCURRENCY","CUBE","DATA","DESC","DIVISION","END","EXCLUDES","FIELDS","FIND|10","FIRST","FOR","FROM",/GROUP\s+BY/,"HAVING","INCLUDES","LAST","LAST_90_DAYS","LAST_MONTH","LAST_N_DAYS","LAST_WEEK","LAST","LIKE","LIMIT","NETWORK","NEXT_90_DAYS","NEXT_MONTH","NEXT_N_DAYS","NEXT_WEEK","NULLS","OFFSET","ON","OR",/ORDER\s+BY/,"REFERENCE","RETURNING","ROLLUP","ROWS","SEARCH","SECURITY_ENFORCED","SELECT","SNIPPET","SORT","THIS_MONTH","THIS_WEEK","TODAY","TOLABEL","TOMORROW","TRACKING","TYPEOF","UPDATE",/USING\s+SCOPE/,"VIEW","VIEWSTAT","VIEWSTATE","WHERE","WITH","YESTERDAY","USER_MODE"),/\b/),
-scope:"keyword"},{match:/(\bIN\b|<|<=|>|>=|\bNOT\s+IN\b|=|!\s*=|\s:{1}|:{1}\s)/,
+begin:[/(?<=\s)/,t.concat("(?:"+a+")"),/(?=\s*\()/],beginScope:{
+2:"title.function"}}],end:/(?=\))/,returnEnd:!0,
+contains:[b,p,e.APOS_STRING_MODE,E,v],relevance:0},R=[{
+begin:[/\b(?<=enum)\s+/,a,/\s*/,/[{()]/],beginScope:{2:"type",4:"punctuation"},
+end:/[})]/,endScope:"punctuation",relevance:0,contains:[b,p,{
+match:t.concat(/\b/,a,/\b/),scope:"variable.constant"}]},{
+match:[/(?<=\bclass\b)/,/\s+/,a],scope:{1:"keyword",3:"title.class"}},{
+begin:[/(?<=(public|private))/,/\s+/,a,/(?=\s*\()/],beginScope:{3:"constructor"
+},end:/\)\s*{/,contains:[E]},{
+begin:[/(?<=\btrigger\b)/,/\s+/,a,/\s+/,"on",/\s+/,a],end:"{",scope:{
+1:"keyword",3:"title.class",5:"operator",7:"type"},contains:[b,p,{
+match:/(?:before|after)\s+(?:insert|update|delete|undelete)/,scope:"built_in",
+relevance:10}],relevance:10},{begin:/(?<=extends|implements)\s*/,end:"{",
+contains:[r,{match:[/\b/,a,/\b/],scope:{2:"title.class.inherited"}},{
+match:[/\b/,a,/\./,a,/\b/],scope:{2:"title.class.inherited",
+4:"title.class.inherited"}}]}],T=[{match:[a,/\s+/,a,/\s+/,/=/],scope:{1:"type",
+3:"variable",5:"operator"},relevance:0},{match:[a,/\s+/,a,/\s+/,";"],scope:{
+1:"type",3:"variable"},relevance:0},{match:[/\s+/,a,/\s+/,/=/],scope:{
+2:"variable",4:"operator"},relevance:0}],O=[{
+match:[/(?<=return)/,/\s+/,a,/(?=\s*;)/],scope:{3:"variable"}},{
+begin:[/(?<=return)/,/\s+/,a,/\(/],end:/\)/,beginScope:{
+3:"title.function.invoke"},contains:[E]}],N={begin:/\[\s*\b(SELECT|FIND)\b/,
+end:/\]/,scope:"soql",relevance:10,contains:[{
+begin:t.concat(/\b/,t.either("ABOVE_OR_BELOW","ACTIVE","ADVANCED","ALL","ANY","ARRAY","AS","BY","CATEGORY","CONTAINS","CUSTOM","DATA","DIVISION","END","FIELDS","FIND|10","LAST","METADATA","NETWORK","ON","RETURNING","ROLLUP","ROWS","SEARCH","SECURITY_ENFORCED","SELECT","SNIPPET","SORT","STANDARD","USER_MODE","WHERE"),/\b/),
+scope:"keyword"},{
+match:t.concat(/\b/,t.either(":","ABOVE","AND","ASC","AT","DESC","DISTANCE","FROM","HAVING","IN","LIKE","LIMIT","LISTVIEW","NOT","OFFSET","OR","TRACKING","TYPEOF","UPDATE","USING","VIEWSTAT","YESTERDAY",/FOR\s+REFERENCE/,/FOR\s+UPDATE/,/FOR\s+VIEW/,/GROUP\s+BY/,/NOT\s+IN/,/NULLS\s+FIRST/,/NULLS\s+LAST/,/ORDER\s+BY/,/WITH\s+DATA\s+CATEGORY/,/WITH\s+PricebookId/),/\b/),
+scope:"operator",relevance:0},{
+match:t.concat(/\b/,t.either("AVG","convertCurrency","convertTimezone","COUNT","COUNT_DISTINCT","DAY_IN_MONTH","DAY_IN_WEEK","DAY_IN_YEAR","DAY_ONLY","toLabel","INCLUDES","EXCLUDES","FORMAT","GROUPING","GROUP BY CUBE","GROUP BY ROLLUP","HOUR_IN_DAY","MAX","MIN","SUM","WEEK_IN_MONTH","WEEK_IN_YEAR"),/\b/),
+scope:"title.function.invoke"},{
+match:/(NEXT|LAST|THIS)_(N_)?(90_DAY|DAY|FISCAL_QUARTER|FISCAL_YEAR|MONTH|QUARTER|WEEK|YEAR)S?/,
+scope:"literal"},{
+match:t.concat(/\b/,t.either("CALENDAR_MONTH","CALENDAR_QUARTER","CALENDAR_YEAR","FISCAL_MONTH","FISCAL_QUARTER","FISCAL_YEAR","TODAY","TOMORROW"),/\b/),
 scope:"literal"},{match:/(?<=\bFROM\b\s+)\w+/,scope:"type",relevance:0},{
-match:[t.concat(/\b/,"[a-zA-Z][a-zA-Z_]*"),":",/[0-9]+\b/],scope:{1:"keyword",
-3:"number"},relevance:10},a,E,e.APOS_STRING_MODE],illegal:"::"};return{
+match:[/\bUSING\s+SCOPE\b\s+/,t.either("Delegated","Everything","Mine","My_Territory","My_Team_Territory","Team")],
+scope:{1:"variable.language",2:"variable.language"}},{
+match:[/(NEXT|LAST|THIS)_(N_)?(90_DAY|DAY|FISCAL_QUARTER|FISCAL_YEAR|MONTH|QUARTER|WEEK|YEAR)S?/,/\s*/,":",/\s*/,/[0-9]+\b/],
+scope:{1:"variable.language",3:"punctuation",5:"number"},relevance:10},{
+match:[/(?<=:)/,/\s*/,a],scope:{3:"variable"}},{match:/[(:)]/,
+scope:"punctuation",relevance:0},s,g,e.APOS_STRING_MODE,o],illegal:"::"},_={
+variants:[{match:[/\bfor\b/,/\s*\(/,a,/\s+/,a,/\s*:/,/(?=\s*\[)/],scope:{
+1:"keyword",3:"type",5:"variable",6:"punctuation"}},{
+match:[/\bfor\b/,/\s*\(/,a,/\s+/,a,/\s*:/,/\s*/,a],scope:{1:"keyword",
+2:"punctuation",3:"type",5:"variable",8:"variable"}}],contains:[b,p,N]};return{
 name:"Apex",aliases:["apex","lightning"],case_insensitive:!0,
-disableAutodetect:!1,ignoreIllegals:!1,keywords:c,
-illegal:["","\x3c!--","!DOCTYPE",/","\x3c!--","!DOCTYPE",//],
+contains:[m,R,T,{relevance:10,match:/\b(with|without|inherited)\s+sharing\b/,
+scope:"keyword"},S,h,p,b,{match:/\b(switch\s+on|as\s+user|as\s+system)\b/,
+relevance:8,scope:"keyword"},d,{
+match:["AccessLevel",".",/(SYSTEM_MODE|USER_MODE)/],scope:{1:"built_in",
+3:"keyword"}},A,_,e.APOS_STRING_MODE,v,g,r,s,o,l,O,{match:/(?= 6"
 			}
 		},
+		"node_modules/glob/node_modules/brace-expansion": {
+			"version": "1.1.11",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+			"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+			"dev": true,
+			"dependencies": {
+				"balanced-match": "^1.0.0",
+				"concat-map": "0.0.1"
+			}
+		},
 		"node_modules/glob/node_modules/minimatch": {
 			"version": "3.1.2",
 			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -426,13 +435,13 @@
 			}
 		},
 		"node_modules/handlebars": {
-			"version": "4.7.7",
-			"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
-			"integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==",
+			"version": "4.7.8",
+			"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
+			"integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
 			"dev": true,
 			"dependencies": {
 				"minimist": "^1.2.5",
-				"neo-async": "^2.6.0",
+				"neo-async": "^2.6.2",
 				"source-map": "^0.6.1",
 				"wordwrap": "^1.0.0"
 			},
@@ -465,9 +474,9 @@
 			}
 		},
 		"node_modules/highlight.js": {
-			"version": "11.8.0",
-			"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.8.0.tgz",
-			"integrity": "sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg==",
+			"version": "11.9.0",
+			"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz",
+			"integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==",
 			"engines": {
 				"node": ">=12.0.0"
 			}
@@ -475,7 +484,7 @@
 		"node_modules/inflight": {
 			"version": "1.0.6",
 			"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
-			"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+			"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
 			"dev": true,
 			"dependencies": {
 				"once": "^1.3.0",
@@ -503,7 +512,7 @@
 		"node_modules/is-extglob": {
 			"version": "2.1.1",
 			"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
-			"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+			"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
 			"dev": true,
 			"engines": {
 				"node": ">=0.10.0"
@@ -627,15 +636,6 @@
 				"node": ">=10"
 			}
 		},
-		"node_modules/minimatch/node_modules/brace-expansion": {
-			"version": "2.0.1",
-			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
-			"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
-			"dev": true,
-			"dependencies": {
-				"balanced-match": "^1.0.0"
-			}
-		},
 		"node_modules/minimist": {
 			"version": "1.2.8",
 			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
@@ -710,9 +710,9 @@
 			"dev": true
 		},
 		"node_modules/node-fetch": {
-			"version": "2.6.9",
-			"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
-			"integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==",
+			"version": "2.7.0",
+			"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+			"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
 			"dev": true,
 			"dependencies": {
 				"whatwg-url": "^5.0.0"
@@ -741,7 +741,7 @@
 		"node_modules/once": {
 			"version": "1.4.0",
 			"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-			"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+			"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
 			"dev": true,
 			"dependencies": {
 				"wrappy": "1"
@@ -801,7 +801,7 @@
 		"node_modules/path-is-absolute": {
 			"version": "1.0.1",
 			"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-			"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+			"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
 			"dev": true,
 			"engines": {
 				"node": ">=0.10.0"
@@ -858,7 +858,7 @@
 		"node_modules/require-directory": {
 			"version": "2.1.1",
 			"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
-			"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+			"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
 			"dev": true,
 			"engines": {
 				"node": ">=0.10.0"
@@ -885,9 +885,9 @@
 			]
 		},
 		"node_modules/semver": {
-			"version": "7.3.8",
-			"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-			"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+			"version": "7.5.4",
+			"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+			"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
 			"dev": true,
 			"dependencies": {
 				"lru-cache": "^6.0.0"
@@ -933,7 +933,7 @@
 		"node_modules/should-format": {
 			"version": "3.0.3",
 			"resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz",
-			"integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=",
+			"integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==",
 			"dev": true,
 			"dependencies": {
 				"should-type": "^1.3.0",
@@ -943,7 +943,7 @@
 		"node_modules/should-type": {
 			"version": "1.4.0",
 			"resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz",
-			"integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=",
+			"integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==",
 			"dev": true
 		},
 		"node_modules/should-type-adaptors": {
@@ -1103,7 +1103,7 @@
 		"node_modules/wrappy": {
 			"version": "1.0.2",
 			"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-			"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+			"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
 			"dev": true
 		},
 		"node_modules/y18n": {
@@ -1175,837 +1175,5 @@
 				"url": "https://github.com/sponsors/sindresorhus"
 			}
 		}
-	},
-	"dependencies": {
-		"ansi-colors": {
-			"version": "4.1.1",
-			"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
-			"integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
-			"dev": true
-		},
-		"ansi-regex": {
-			"version": "5.0.1",
-			"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-			"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-			"dev": true
-		},
-		"ansi-styles": {
-			"version": "4.3.0",
-			"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-			"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-			"dev": true,
-			"requires": {
-				"color-convert": "^2.0.1"
-			}
-		},
-		"anymatch": {
-			"version": "3.1.2",
-			"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
-			"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
-			"dev": true,
-			"requires": {
-				"normalize-path": "^3.0.0",
-				"picomatch": "^2.0.4"
-			}
-		},
-		"argparse": {
-			"version": "2.0.1",
-			"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
-			"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
-			"dev": true
-		},
-		"auto-changelog": {
-			"version": "2.4.0",
-			"resolved": "https://registry.npmjs.org/auto-changelog/-/auto-changelog-2.4.0.tgz",
-			"integrity": "sha512-vh17hko1c0ItsEcw6m7qPRf3m45u+XK5QyCrrBFViElZ8jnKrPC1roSznrd1fIB/0vR/zawdECCRJtTuqIXaJw==",
-			"dev": true,
-			"requires": {
-				"commander": "^7.2.0",
-				"handlebars": "^4.7.7",
-				"node-fetch": "^2.6.1",
-				"parse-github-url": "^1.0.2",
-				"semver": "^7.3.5"
-			}
-		},
-		"balanced-match": {
-			"version": "1.0.2",
-			"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
-			"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
-			"dev": true
-		},
-		"binary-extensions": {
-			"version": "2.2.0",
-			"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
-			"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
-			"dev": true
-		},
-		"brace-expansion": {
-			"version": "1.1.11",
-			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
-			"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
-			"dev": true,
-			"requires": {
-				"balanced-match": "^1.0.0",
-				"concat-map": "0.0.1"
-			}
-		},
-		"braces": {
-			"version": "3.0.2",
-			"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-			"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
-			"dev": true,
-			"requires": {
-				"fill-range": "^7.0.1"
-			}
-		},
-		"browser-stdout": {
-			"version": "1.3.1",
-			"resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
-			"integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
-			"dev": true
-		},
-		"camelcase": {
-			"version": "6.3.0",
-			"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
-			"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
-			"dev": true
-		},
-		"chalk": {
-			"version": "4.1.2",
-			"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-			"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-			"dev": true,
-			"requires": {
-				"ansi-styles": "^4.1.0",
-				"supports-color": "^7.1.0"
-			},
-			"dependencies": {
-				"supports-color": {
-					"version": "7.2.0",
-					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
-					"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-					"dev": true,
-					"requires": {
-						"has-flag": "^4.0.0"
-					}
-				}
-			}
-		},
-		"chokidar": {
-			"version": "3.5.3",
-			"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
-			"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
-			"dev": true,
-			"requires": {
-				"anymatch": "~3.1.2",
-				"braces": "~3.0.2",
-				"fsevents": "~2.3.2",
-				"glob-parent": "~5.1.2",
-				"is-binary-path": "~2.1.0",
-				"is-glob": "~4.0.1",
-				"normalize-path": "~3.0.0",
-				"readdirp": "~3.6.0"
-			}
-		},
-		"cliui": {
-			"version": "7.0.4",
-			"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
-			"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
-			"dev": true,
-			"requires": {
-				"string-width": "^4.2.0",
-				"strip-ansi": "^6.0.0",
-				"wrap-ansi": "^7.0.0"
-			}
-		},
-		"color-convert": {
-			"version": "2.0.1",
-			"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-			"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-			"dev": true,
-			"requires": {
-				"color-name": "~1.1.4"
-			}
-		},
-		"color-name": {
-			"version": "1.1.4",
-			"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-			"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-			"dev": true
-		},
-		"commander": {
-			"version": "7.2.0",
-			"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
-			"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
-			"dev": true
-		},
-		"concat-map": {
-			"version": "0.0.1",
-			"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
-			"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
-			"dev": true
-		},
-		"debug": {
-			"version": "4.3.4",
-			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
-			"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
-			"dev": true,
-			"requires": {
-				"ms": "2.1.2"
-			},
-			"dependencies": {
-				"ms": {
-					"version": "2.1.2",
-					"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-					"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-					"dev": true
-				}
-			}
-		},
-		"decamelize": {
-			"version": "4.0.0",
-			"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
-			"integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
-			"dev": true
-		},
-		"diff": {
-			"version": "5.0.0",
-			"resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
-			"integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
-			"dev": true
-		},
-		"emoji-regex": {
-			"version": "8.0.0",
-			"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-			"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
-			"dev": true
-		},
-		"escalade": {
-			"version": "3.1.1",
-			"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
-			"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
-			"dev": true
-		},
-		"escape-string-regexp": {
-			"version": "4.0.0",
-			"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
-			"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
-			"dev": true
-		},
-		"fill-range": {
-			"version": "7.0.1",
-			"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-			"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
-			"dev": true,
-			"requires": {
-				"to-regex-range": "^5.0.1"
-			}
-		},
-		"find-up": {
-			"version": "5.0.0",
-			"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
-			"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
-			"dev": true,
-			"requires": {
-				"locate-path": "^6.0.0",
-				"path-exists": "^4.0.0"
-			}
-		},
-		"flat": {
-			"version": "5.0.2",
-			"resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
-			"integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
-			"dev": true
-		},
-		"fs.realpath": {
-			"version": "1.0.0",
-			"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-			"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
-			"dev": true
-		},
-		"fsevents": {
-			"version": "2.3.2",
-			"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
-			"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
-			"dev": true,
-			"optional": true
-		},
-		"get-caller-file": {
-			"version": "2.0.5",
-			"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
-			"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
-			"dev": true
-		},
-		"glob": {
-			"version": "7.2.0",
-			"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
-			"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
-			"dev": true,
-			"requires": {
-				"fs.realpath": "^1.0.0",
-				"inflight": "^1.0.4",
-				"inherits": "2",
-				"minimatch": "^3.0.4",
-				"once": "^1.3.0",
-				"path-is-absolute": "^1.0.0"
-			},
-			"dependencies": {
-				"minimatch": {
-					"version": "3.1.2",
-					"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
-					"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
-					"dev": true,
-					"requires": {
-						"brace-expansion": "^1.1.7"
-					}
-				}
-			}
-		},
-		"glob-parent": {
-			"version": "5.1.2",
-			"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
-			"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
-			"dev": true,
-			"requires": {
-				"is-glob": "^4.0.1"
-			}
-		},
-		"handlebars": {
-			"version": "4.7.7",
-			"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
-			"integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==",
-			"dev": true,
-			"requires": {
-				"minimist": "^1.2.5",
-				"neo-async": "^2.6.0",
-				"source-map": "^0.6.1",
-				"uglify-js": "^3.1.4",
-				"wordwrap": "^1.0.0"
-			}
-		},
-		"has-flag": {
-			"version": "4.0.0",
-			"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-			"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-			"dev": true
-		},
-		"he": {
-			"version": "1.2.0",
-			"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
-			"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
-			"dev": true
-		},
-		"highlight.js": {
-			"version": "11.8.0",
-			"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.8.0.tgz",
-			"integrity": "sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg=="
-		},
-		"inflight": {
-			"version": "1.0.6",
-			"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
-			"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
-			"dev": true,
-			"requires": {
-				"once": "^1.3.0",
-				"wrappy": "1"
-			}
-		},
-		"inherits": {
-			"version": "2.0.4",
-			"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
-			"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
-			"dev": true
-		},
-		"is-binary-path": {
-			"version": "2.1.0",
-			"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
-			"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
-			"dev": true,
-			"requires": {
-				"binary-extensions": "^2.0.0"
-			}
-		},
-		"is-extglob": {
-			"version": "2.1.1",
-			"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
-			"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
-			"dev": true
-		},
-		"is-fullwidth-code-point": {
-			"version": "3.0.0",
-			"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
-			"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
-			"dev": true
-		},
-		"is-glob": {
-			"version": "4.0.3",
-			"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
-			"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
-			"dev": true,
-			"requires": {
-				"is-extglob": "^2.1.1"
-			}
-		},
-		"is-number": {
-			"version": "7.0.0",
-			"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
-			"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
-			"dev": true
-		},
-		"is-plain-obj": {
-			"version": "2.1.0",
-			"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
-			"integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
-			"dev": true
-		},
-		"is-unicode-supported": {
-			"version": "0.1.0",
-			"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
-			"integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
-			"dev": true
-		},
-		"js-yaml": {
-			"version": "4.1.0",
-			"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
-			"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
-			"dev": true,
-			"requires": {
-				"argparse": "^2.0.1"
-			}
-		},
-		"locate-path": {
-			"version": "6.0.0",
-			"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
-			"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
-			"dev": true,
-			"requires": {
-				"p-locate": "^5.0.0"
-			}
-		},
-		"log-symbols": {
-			"version": "4.1.0",
-			"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
-			"integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
-			"dev": true,
-			"requires": {
-				"chalk": "^4.1.0",
-				"is-unicode-supported": "^0.1.0"
-			}
-		},
-		"lru-cache": {
-			"version": "6.0.0",
-			"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-			"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-			"dev": true,
-			"requires": {
-				"yallist": "^4.0.0"
-			}
-		},
-		"minimatch": {
-			"version": "5.0.1",
-			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
-			"integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
-			"dev": true,
-			"requires": {
-				"brace-expansion": "^2.0.1"
-			},
-			"dependencies": {
-				"brace-expansion": {
-					"version": "2.0.1",
-					"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
-					"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
-					"dev": true,
-					"requires": {
-						"balanced-match": "^1.0.0"
-					}
-				}
-			}
-		},
-		"minimist": {
-			"version": "1.2.8",
-			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
-			"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
-			"dev": true
-		},
-		"mocha": {
-			"version": "10.2.0",
-			"resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz",
-			"integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==",
-			"dev": true,
-			"requires": {
-				"ansi-colors": "4.1.1",
-				"browser-stdout": "1.3.1",
-				"chokidar": "3.5.3",
-				"debug": "4.3.4",
-				"diff": "5.0.0",
-				"escape-string-regexp": "4.0.0",
-				"find-up": "5.0.0",
-				"glob": "7.2.0",
-				"he": "1.2.0",
-				"js-yaml": "4.1.0",
-				"log-symbols": "4.1.0",
-				"minimatch": "5.0.1",
-				"ms": "2.1.3",
-				"nanoid": "3.3.3",
-				"serialize-javascript": "6.0.0",
-				"strip-json-comments": "3.1.1",
-				"supports-color": "8.1.1",
-				"workerpool": "6.2.1",
-				"yargs": "16.2.0",
-				"yargs-parser": "20.2.4",
-				"yargs-unparser": "2.0.0"
-			}
-		},
-		"ms": {
-			"version": "2.1.3",
-			"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
-			"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
-			"dev": true
-		},
-		"nanoid": {
-			"version": "3.3.3",
-			"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
-			"integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
-			"dev": true
-		},
-		"neo-async": {
-			"version": "2.6.2",
-			"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
-			"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
-			"dev": true
-		},
-		"node-fetch": {
-			"version": "2.6.9",
-			"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
-			"integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==",
-			"dev": true,
-			"requires": {
-				"whatwg-url": "^5.0.0"
-			}
-		},
-		"normalize-path": {
-			"version": "3.0.0",
-			"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
-			"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
-			"dev": true
-		},
-		"once": {
-			"version": "1.4.0",
-			"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-			"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
-			"dev": true,
-			"requires": {
-				"wrappy": "1"
-			}
-		},
-		"p-limit": {
-			"version": "3.1.0",
-			"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
-			"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
-			"dev": true,
-			"requires": {
-				"yocto-queue": "^0.1.0"
-			}
-		},
-		"p-locate": {
-			"version": "5.0.0",
-			"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
-			"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
-			"dev": true,
-			"requires": {
-				"p-limit": "^3.0.2"
-			}
-		},
-		"parse-github-url": {
-			"version": "1.0.2",
-			"resolved": "https://registry.npmjs.org/parse-github-url/-/parse-github-url-1.0.2.tgz",
-			"integrity": "sha512-kgBf6avCbO3Cn6+RnzRGLkUsv4ZVqv/VfAYkRsyBcgkshNvVBkRn1FEZcW0Jb+npXQWm2vHPnnOqFteZxRRGNw==",
-			"dev": true
-		},
-		"path-exists": {
-			"version": "4.0.0",
-			"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
-			"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
-			"dev": true
-		},
-		"path-is-absolute": {
-			"version": "1.0.1",
-			"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-			"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
-			"dev": true
-		},
-		"picomatch": {
-			"version": "2.3.1",
-			"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
-			"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
-			"dev": true
-		},
-		"prettier": {
-			"version": "3.0.3",
-			"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
-			"integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
-			"dev": true
-		},
-		"randombytes": {
-			"version": "2.1.0",
-			"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
-			"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
-			"dev": true,
-			"requires": {
-				"safe-buffer": "^5.1.0"
-			}
-		},
-		"readdirp": {
-			"version": "3.6.0",
-			"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
-			"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
-			"dev": true,
-			"requires": {
-				"picomatch": "^2.2.1"
-			}
-		},
-		"require-directory": {
-			"version": "2.1.1",
-			"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
-			"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
-			"dev": true
-		},
-		"safe-buffer": {
-			"version": "5.2.1",
-			"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
-			"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
-			"dev": true
-		},
-		"semver": {
-			"version": "7.3.8",
-			"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
-			"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
-			"dev": true,
-			"requires": {
-				"lru-cache": "^6.0.0"
-			}
-		},
-		"serialize-javascript": {
-			"version": "6.0.0",
-			"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
-			"integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
-			"dev": true,
-			"requires": {
-				"randombytes": "^2.1.0"
-			}
-		},
-		"should": {
-			"version": "13.2.3",
-			"resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz",
-			"integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==",
-			"dev": true,
-			"requires": {
-				"should-equal": "^2.0.0",
-				"should-format": "^3.0.3",
-				"should-type": "^1.4.0",
-				"should-type-adaptors": "^1.0.1",
-				"should-util": "^1.0.0"
-			}
-		},
-		"should-equal": {
-			"version": "2.0.0",
-			"resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz",
-			"integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==",
-			"dev": true,
-			"requires": {
-				"should-type": "^1.4.0"
-			}
-		},
-		"should-format": {
-			"version": "3.0.3",
-			"resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz",
-			"integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=",
-			"dev": true,
-			"requires": {
-				"should-type": "^1.3.0",
-				"should-type-adaptors": "^1.0.1"
-			}
-		},
-		"should-type": {
-			"version": "1.4.0",
-			"resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz",
-			"integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=",
-			"dev": true
-		},
-		"should-type-adaptors": {
-			"version": "1.1.0",
-			"resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz",
-			"integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==",
-			"dev": true,
-			"requires": {
-				"should-type": "^1.3.0",
-				"should-util": "^1.0.0"
-			}
-		},
-		"should-util": {
-			"version": "1.0.1",
-			"resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz",
-			"integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==",
-			"dev": true
-		},
-		"source-map": {
-			"version": "0.6.1",
-			"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-			"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-			"dev": true
-		},
-		"string-width": {
-			"version": "4.2.3",
-			"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-			"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-			"dev": true,
-			"requires": {
-				"emoji-regex": "^8.0.0",
-				"is-fullwidth-code-point": "^3.0.0",
-				"strip-ansi": "^6.0.1"
-			}
-		},
-		"strip-ansi": {
-			"version": "6.0.1",
-			"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-			"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-			"dev": true,
-			"requires": {
-				"ansi-regex": "^5.0.1"
-			}
-		},
-		"strip-json-comments": {
-			"version": "3.1.1",
-			"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
-			"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
-			"dev": true
-		},
-		"supports-color": {
-			"version": "8.1.1",
-			"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
-			"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
-			"dev": true,
-			"requires": {
-				"has-flag": "^4.0.0"
-			}
-		},
-		"to-regex-range": {
-			"version": "5.0.1",
-			"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
-			"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
-			"dev": true,
-			"requires": {
-				"is-number": "^7.0.0"
-			}
-		},
-		"tr46": {
-			"version": "0.0.3",
-			"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
-			"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
-			"dev": true
-		},
-		"uglify-js": {
-			"version": "3.17.4",
-			"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz",
-			"integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==",
-			"dev": true,
-			"optional": true
-		},
-		"webidl-conversions": {
-			"version": "3.0.1",
-			"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
-			"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
-			"dev": true
-		},
-		"whatwg-url": {
-			"version": "5.0.0",
-			"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
-			"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
-			"dev": true,
-			"requires": {
-				"tr46": "~0.0.3",
-				"webidl-conversions": "^3.0.0"
-			}
-		},
-		"wordwrap": {
-			"version": "1.0.0",
-			"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
-			"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
-			"dev": true
-		},
-		"workerpool": {
-			"version": "6.2.1",
-			"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
-			"integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
-			"dev": true
-		},
-		"wrap-ansi": {
-			"version": "7.0.0",
-			"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
-			"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
-			"dev": true,
-			"requires": {
-				"ansi-styles": "^4.0.0",
-				"string-width": "^4.1.0",
-				"strip-ansi": "^6.0.0"
-			}
-		},
-		"wrappy": {
-			"version": "1.0.2",
-			"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-			"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
-			"dev": true
-		},
-		"y18n": {
-			"version": "5.0.8",
-			"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
-			"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
-			"dev": true
-		},
-		"yallist": {
-			"version": "4.0.0",
-			"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-			"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
-			"dev": true
-		},
-		"yargs": {
-			"version": "16.2.0",
-			"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
-			"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
-			"dev": true,
-			"requires": {
-				"cliui": "^7.0.2",
-				"escalade": "^3.1.1",
-				"get-caller-file": "^2.0.5",
-				"require-directory": "^2.1.1",
-				"string-width": "^4.2.0",
-				"y18n": "^5.0.5",
-				"yargs-parser": "^20.2.2"
-			}
-		},
-		"yargs-parser": {
-			"version": "20.2.4",
-			"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
-			"integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
-			"dev": true
-		},
-		"yargs-unparser": {
-			"version": "2.0.0",
-			"resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
-			"integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
-			"dev": true,
-			"requires": {
-				"camelcase": "^6.0.0",
-				"decamelize": "^4.0.0",
-				"flat": "^5.0.2",
-				"is-plain-obj": "^2.1.0"
-			}
-		},
-		"yocto-queue": {
-			"version": "0.1.0",
-			"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
-			"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
-			"dev": true
-		}
 	}
 }
diff --git a/package.json b/package.json
index 7e5965b..d1f4a56 100644
--- a/package.json
+++ b/package.json
@@ -35,7 +35,7 @@
 	},
 	"homepage": "https://github.com/highlightjs/highlightjs-apex#readme",
 	"dependencies": {
-		"highlight.js": "11.8.0"
+		"highlight.js": "11.9.0"
 	},
 	"devDependencies": {
 		"auto-changelog": "^2.4.0",
diff --git a/scripts/cheatsheet.txt b/scripts/cheatsheet.txt
new file mode 100644
index 0000000..947c18f
--- /dev/null
+++ b/scripts/cheatsheet.txt
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+## build for webpage
+npm run build-cdn apex
+
+## Set up developer.html
+# build just common set
+node ./tools/build -t browser :common
+
+# build common set + 3rd party `apex` from extra
+node ./tools/build -t browser :common apex
+
+# build everything (including all in extra)
+node ./tools/build -t browser 
\ No newline at end of file
diff --git a/src/languages/apex.js b/src/languages/apex.js
index 9fef05f..55e252f 100644
--- a/src/languages/apex.js
+++ b/src/languages/apex.js
@@ -9,41 +9,29 @@ Website: https://developer.salesforce.com/
 export default function (hljs) {
   const regex = hljs.regex;
   const APEX_IDENT_RE = '[a-zA-Z][a-zA-Z_0-9]*';
-  const APEX_ALPHA_UNDER = '[a-zA-Z][a-zA-Z_]*';
+  //const APEX_ALPHA_UNDER = '[a-zA-Z][a-zA-Z_]*';
   //const APEX_FULL_TYPE_RE = '[a-zA-Z][a-zA-Z_0-9.<>]*';
 
   const NUMBER = {
     scope: 'number',
     variants: [
       {
-        match: /\b[0-9]+(?:\.[0-9]+)?/
+        match: /(-?)\b[0-9]+(?:\.[0-9]+)?/
       },
-      {
-        match: /\s(?:[0-9,]+)?\.[0-9]+/
-      },
-      {
-        // hex
-        // 0b binary-digits integer-type-suffix[opt] OR 0B binary-digits integer-type-suffix[opt]
-        match: /\b0(x|X)[0-9a-fA-F_]+(U|u|L|l|UL|Ul|uL|ul|LU|Lu|lU|lu)?\b/
-      },
-      {
-        // numeric binary
-        match: /\b0(b|B)[01_]+(U|u|L|l|UL|Ul|uL|ul|LU|Lu|lU|lu)?\b/
-      },
-      {
+       {
         // numeric decimal
         // decimal-digits . decimal-digits exponent-part[opt] real-type-suffix[opt] OR . decimal-digits exponent-part[opt] real-type-suffix[opt]
-        match: /\b([0-9_]+)?\.[0-9_]+((e|E)[0-9]+)?(F|f|D|d|M|m)?\b/
+        match: /(-?)\b([0-9_]+)?\.[0-9_]+((e|E)[0-9]+)?(F|f|D|d|M|m)?\b/
       },
       {
         // numeric decimal
         // decimal-digits exponent-part real-type-suffix[opt]
-        match: /\b[0-9_]+(e|E)[0-9_]+(F|f|D|d|M|m)?\b/
+        match: /(-?)\b[0-9_]+(e|E)[0-9_]+(F|f|D|d|M|m)?\b/
       },
       {
         // numeric decimal
         // decimal-digits real-type-suffix
-        match: /\b[0-9_]+(F|f|D|d|M|m)\b/
+        match: /(-?)\b[0-9_]+(F|f|D|d|M|m)\b/
       },
       {
         // numeric decimal
@@ -59,10 +47,6 @@ export default function (hljs) {
     'class',
     'interface',
     'abstract',
-    'AccessLevel',
-    'USER_MODE',
-    'SYSTEM_MODE',
-    'AccessType',
     'break',
     'cast',
     'catch',
@@ -73,7 +57,6 @@ export default function (hljs) {
     'exports',
     'extends|6',
     'finally',
-    'for',
     'get',
     'put',
     'set',
@@ -171,15 +154,6 @@ export default function (hljs) {
     '@TestVisible'
   ]; */
 
-  const KEYWORDS = {
-    $pattern: '[A-Za-z][0-9A-Za-z$_]*',
-    keyword: MAIN_KEYWORDS,
-    'variable.language': LANGUAGE_VARS,
-    built_in: BUILT_INS.concat(DMLS),
-    type: TYPES,
-    literal: LITERALS
-  };
-
   const COMPOUND_KEYWORDS = {
     match: /\b(switch\s+on|as\s+user|as\s+system)\b/,
     relevance: 8,
@@ -190,7 +164,6 @@ export default function (hljs) {
     'ApexPages|10',
     'AppLauncher',
     'Approval',
-    'Assert',
     'Auth',
     'Cache',
     'Canvas',
@@ -237,6 +210,152 @@ export default function (hljs) {
     'Wave|10'
   ];
 
+  const SYSTEM_CLASSES = [
+    'AccessLevel',
+    'Address',
+    'Answers',
+    'ApexPages',
+    'Approval',
+    'Assert',
+    'AsyncInfo',
+    'AsyncOptions',
+    'BusinessHours',
+    'Cases',
+    'Collator',
+    'Continuation',
+    'Cookie',
+    'Crypto',
+    'Database',
+    'Date',
+    'Datetime',
+    'Decimal',
+    'Domain',
+    'DomainCreator',
+    'DomainParser',
+    'EmailMessages',
+    'EncodingUtil',
+    'EventBus',
+    'Exception',
+    'FeatureManagement',
+    'FlexQueue',
+    'Formula',
+    'FormulaRecalcFieldError',
+    'FormulaRecalcResult',
+    'Http',
+    'HttpRequest',
+    'HttpResponse',
+    'Ideas',
+    'JSON',
+    'JSONGenerator',
+    'JSONParser',
+    'Label',
+    'Limits',
+    'Location',
+    'Matcher',
+    'Math',
+    'Messaging',
+    'MultiStaticResourceCalloutMock',
+    'Network',
+    'OrgLimit',
+    'OrgLimits',
+    'Packaging',
+    'PageReference',
+    'Pattern',
+    'QueueableDuplicateSignature',
+    'QueueableDuplicateSignature.Builder',
+    'QuickAction',
+    'Request',
+    'ResetPasswordResult',
+    'RestContext',
+    'RestRequest',
+    'RestResponse',
+    'Schema',
+    'Search',
+    'Security',
+    'SelectOption',
+    'Site',
+    'SObject',
+    'SObjectAccessDecision',
+    'StaticResourceCalloutMock',
+    'TimeZone',
+    'Type',
+    'URL',
+    'UserInfo',
+    'UserManagement',
+    'Version',
+    'WebServiceCallout',
+    'XmlStreamReader',
+    'XmlStreamWriter'
+  ];
+
+  const SYSTEM_ENUMS = [
+    'AccessType',
+    'DomainType',
+    'JSONToken',
+    'LoggingLevel',
+    'Quiddity',
+    'TriggerOperation'
+  ];
+
+  const SYSTEM_INTERFACES = [
+    'Callable',
+    'Comparable',
+    'Comparator',
+    'HttpCalloutMock',
+    'InstallHandler',
+    'Queueable',
+    'QueueableContext',
+    'SandboxPostCopy',
+    'Schedulable',
+    'SchedulableContext',
+    'StubProvider',
+    'UninstallHandler',
+    'WebServiceMock'
+  ];
+
+  const SYSTEM_INVOKE = [
+    {
+      match: [
+        /\b/,
+        regex.either(...SYSTEM_CLASSES),
+        /\./
+      ],
+      scope: { 2: 'title' },
+      relevance: 9
+    }
+  ]
+
+  const NAMESPACES = [
+    {
+      match: [
+        /\b/,
+        regex.either(...SYSTEM_INTERFACES),
+        /\b/
+      ],
+      scope: { 2: 'title.class.inherited' },
+      relevance: 10
+    },
+    {
+      match: [/\b/, regex.either(...NAMESPACE_LIST), /\./, APEX_IDENT_RE, /\b/],
+      scope: { 2: 'built_in', 4: 'title.class' }
+    },
+    {
+      match: [/\b/, regex.either(...NAMESPACE_LIST), /(?=\.)/],
+      scope: { 2: 'built_in' },
+      relevance: 10
+    },
+    {
+      match: [/\bSystem/, '.', APEX_IDENT_RE, /(?=\.)/],
+      scope: { 1: 'built_in', 3: 'title.class' },
+      relevance: 10
+    },
+    {
+      match: [/\b/, regex.either(...SYSTEM_ENUMS), /\./, APEX_IDENT_RE, /\b/],
+      // TODO: Find a better scope for the enum value
+      scope: { 2: 'built_in', 4: 'type' }
+    }
+  ];
+
   const OPERATOR_REGEX = [
     /-/,
     /--/,
@@ -244,7 +363,7 @@ export default function (hljs) {
     /\*/,
     /\*=/,
     /\/=/,
-    /%/,
+    /%[^%]/,
     /\+/,
     /\+\+/,
     /</],
+      match: [regex.concat(/\b/, APEX_IDENT_RE, /\b/), '>'],
       scope: {
         1: 'type'
       },
@@ -409,184 +544,154 @@ export default function (hljs) {
     }
   ];
 
-  const CUSTOM_OBJECT = {
+  const CUSTOM_METADATA = {
     // Custom fields, types, etc.
-    match: [/[^\.]/, /\b[a-zA-Z][a-zA-Z\d_]*__[cxebr]\b/, /[\(\s;,]+/],
+    match: [
+      /(\w|[^\.])/,
+      regex.concat(
+        /\b/,
+        APEX_IDENT_RE,
+        /__(c|pc|r|b|e|mdt|x|share|kav|ka|history|del|s)/,
+        //regex.either(...OBJECT_SUFFIXES),
+        /\b/
+      ),
+      /(?=[\(\s;,])/
+    ],
     scope: {
       2: 'type'
     },
     relevance: 10
   };
 
+  const PARAM_VARS = {
+    contains:[NUMBER, hljs.APOS_STRING_MODE],
+    illegal: ':',
+    relevance: 0,
+    variants:[
+    {
+      match: regex.concat(/\b/,regex.either(...LITERALS),/\b/),
+      scope: 'literal'
+    },
+    {
+      // mymethod(c.Id, c.Name); highlights each part of each parameter
+      // must be followed by comma or paren
+      match: [APEX_IDENT_RE, /\./, APEX_IDENT_RE, /\s*(?=[,)])/],
+      scope: { 1: 'variable', 3: 'property' }
+    },
+    {
+      // mymethod(Date myDate, Date yourDate); highlights each part of each parameter
+      // must be followed by comma or paren
+    match: [APEX_IDENT_RE, /\s+/, APEX_IDENT_RE, /\s*(?=[,)])/],
+      scope: { 1: 'type', 3: 'variable' }
+    },
+    {
+      // Parameter type, when declaring a method.
+      // This is a word, with/without a period, followed by a space and then NOT by a comma or paren
+      match: [
+        regex.either(
+          APEX_IDENT_RE,
+          regex.concat(APEX_IDENT_RE, /\./, APEX_IDENT_RE)
+        ),
+        /\s+(?![,)])/
+      ],
+      scope: { 1: 'variable' }
+    },
+    {
+      // Second part of the parameter, followed by comma or paren
+      match: [APEX_IDENT_RE, /\s*(?=[,)])/],
+      scope: { 1: 'variable' }
+   
+    }
+  ]}
+
+  const INSTANTIATE = [
+    {
+      // Account a = new Account(Name = 'test account);
+      begin: [/\bnew\b/, /\s+/, APEX_IDENT_RE, /\s*/, /\(/],
+      beginScope: {
+        1: 'keyword',
+        3: 'title.function.invoke'
+      },
+      end: /(?=\))/,
+      returnEnd: true,
+      scope: 'params',
+      contains: [PARAM_VARS, { match: /\(\)/ }, COMMENT_LINE],
+      illegal: ':'
+    }
+  ];
+
   const METHOD_CALL = {
     variants: [
       {
-        match: [/\./, regex.concat('(?:' + APEX_IDENT_RE + ')'), /(?=\s*\(\))/],
-        scope: { 2: 'title.function.invoke' }
+        begin: [/\./, regex.concat('(?:' + APEX_IDENT_RE + ')'), /(?=\s*\(\))/],
+        beginScope: { 2: 'title.function.invoke' }
       },
-      {
-        match: [
+     /*  {
+        begin: [
           /\./,
           regex.concat('(?:' + APEX_IDENT_RE + ')'),
           /(?=\s*\([^\)])/
         ],
-        scope: { 2: 'title.function.invoke' }
-      },
+        beginScope: { 2: 'title.function.invoke' }
+      }, */
       {
-        match: [
+        begin: [
           /(?<=\s)/,
           regex.concat('(?:' + APEX_IDENT_RE + ')'),
           /(?=\s*\()/
         ],
-        scope: { 2: 'title.function' }
+        beginScope: { 2: 'title.function' }
       }
     ],
-    contains: [COMMENT_LINE, COMMENT_BLOCK, hljs.APOS_STRING_MODE],
+    end: /(?=\))/,
+    returnEnd: true,
+    contains: [COMMENT_LINE, COMMENT_BLOCK, hljs.APOS_STRING_MODE, PARAM_VARS, INSTANTIATE],
     relevance: 0
   };
 
-  const SOQL_KEYWORDS = [
-    'ABOVE_OR_BELOW',
-    'ABOVE',
-    'ACTIVE',
-    'ADVANCED',
-    'ALL',
-    /ALL\s+FIELDS/,
-    'AND',
-    'ANY',
-    'ARRAY',
-    'AS',
-    'ASC',
-    'BY',
-    'CATEGORY',
-    'CONTAINS',
-    'COUNT',
-    'COUNT_DISTINCT',
-    'SUM',
-    'MAX',
-    'MIN',
-    'HOUR_IN_DAY',
-    'CONVERTCURRENCY',
-    'CUBE',
-    'DATA',
-    'DESC',
-    'DIVISION',
-    'END',
-    'EXCLUDES',
-    'FIELDS',
-    'FIND|10',
-    'FIRST',
-    'FOR',
-    'FROM',
-    /GROUP\s+BY/,
-    'HAVING',
-    'INCLUDES',
-    'LAST',
-    'LAST_90_DAYS',
-    'LAST_MONTH',
-    'LAST_N_DAYS',
-    'LAST_WEEK',
-    'LAST',
-    'LIKE',
-    'LIMIT',
-    'NETWORK',
-    'NEXT_90_DAYS',
-    'NEXT_MONTH',
-    'NEXT_N_DAYS',
-    'NEXT_WEEK',
-    'NULLS',
-    'OFFSET',
-    'ON',
-    'OR',
-    /ORDER\s+BY/,
-    'REFERENCE',
-    'RETURNING',
-    'ROLLUP',
-    'ROWS',
-    'SEARCH',
-    'SECURITY_ENFORCED',
-    'SELECT',
-    'SNIPPET',
-    'SORT',
-    'THIS_MONTH',
-    'THIS_WEEK',
-    'TODAY',
-    'TOLABEL',
-    'TOMORROW',
-    'TRACKING',
-    'TYPEOF',
-    'UPDATE',
-    /USING\s+SCOPE/,
-    'VIEW',
-    'VIEWSTAT',
-    'VIEWSTATE',
-    'WHERE',
-    'WITH',
-    'YESTERDAY',
-    'USER_MODE'
-  ];
+  
 
-  const SOQL_QUERY = {
-    begin: /\[[\s\n]*(?=(SELECT|FIND))/,
-    end: /\]/,
-    scope: 'subst',
+  const CLASS_SHARING = {
     relevance: 10,
-    contains: [
-      {
-        begin: regex.concat(/\b/, regex.either(...SOQL_KEYWORDS), /\b/),
-        scope: 'keyword'
-      },
-      {
-        match: /(\bIN\b|<|<=|>|>=|\bNOT\s+IN\b|=|!\s*=|\s:{1}|:{1}\s)/,
-        scope: 'literal'
-      },
-      {
-        match: /(?<=\bFROM\b\s+)\w+/,
-        scope: 'type',
-        relevance: 0
-      },
-      {
-        match: [regex.concat(/\b/, APEX_ALPHA_UNDER), ':', /[0-9]+\b/],
-        scope: {
-          1: 'keyword',
-          3: 'number'
-        },
-        relevance: 10
-      },
-      NUMBER,
-      METHOD_CALL,
-      hljs.APOS_STRING_MODE
-    ],
-    illegal: '::'
+    match: /\b(with|without|inherited)\s+sharing\b/,
+    scope: 'keyword'
+    
   };
 
   const APEX_DECLARATIONS = [
     {
-      match: [/\b(?<=enum|\bnew)/, /\s+/, APEX_IDENT_RE, /\s*(?=[{()])/],
-      scope: {
-        3: 'type'
+      // Enum
+      begin: [/\b(?<=enum)\s+/, APEX_IDENT_RE, /\s*/,/[{()]/],
+      beginScope: {
+        2: 'type',
+        4: 'punctuation'
       },
-      contains: [COMMENT_LINE, COMMENT_BLOCK] // , CUSTOM_OBJECT
+      end: /[})]/,
+      endScope: 'punctuation',
+      relevance: 0,
+      contains: [COMMENT_LINE, COMMENT_BLOCK,
+      {
+        match: regex.concat(/\b/, APEX_IDENT_RE, /\b/),
+        scope: 'variable.constant'
+      }] // , CUSTOM_OBJECT
     },
     // Class Name
     {
       match: [/(?<=\bclass\b)/, /\s+/, APEX_IDENT_RE],
       scope: {
-        //1: 'keyword',
+        1: 'keyword',
         3: 'title.class'
       }
     },
     // Constructor
+    // Matches public/private, methodname, then parens and a curly bracket
     {
-      match: [
-        /(?<=(public|private))/,
-        /\s+/,
-        APEX_IDENT_RE,
-        /(?=\s*\(.*\)\s*{)/
-      ], // /(?=\s*\()/],
-      scope: {
+      begin: [/(?<=(public|private))/, /\s+/, APEX_IDENT_RE, /(?=\s*\()/], // /(?=\s*\()/],
+      beginScope: {
         3: 'constructor'
-      }
+      },
+      end: /\)\s*{/,
+      contains: [PARAM_VARS]
     },
     // Trigger
     {
@@ -601,8 +706,9 @@ export default function (hljs) {
       ],
       end: '{',
       scope: {
-        //1: 'keyword',
+        1: 'keyword',
         3: 'title.class',
+        5: 'operator',
         7: 'type'
       },
       contains: [
@@ -647,73 +753,55 @@ export default function (hljs) {
         COMMENT_BLOCK,
         hljs.APOS_STRING_MODE,
         COLLECTION_DECLARATION,
-        COLLECTION_MAP_VALUE
+        COLLECTION_VALUE
       ],
       relevance: 0,
       illegal: [/\b_/, /_\b/]
     }, */
     // extending
+
+    // TODO: Change to beginKeywords and assign scope 'keyword'
     {
-      match: [/(?:extends)/, /\s+/, APEX_IDENT_RE],
-      scope: {
-        3: 'title.class.inherited'
-      },
-      illegal: [/\b_/, /_\b/]
+      begin: /(?<=extends|implements)\s*/,
+      end: '{',
+      contains: [
+        NAMESPACES,
+        {
+          match: [/\b/, APEX_IDENT_RE, /\b/],
+          scope: { 2: 'title.class.inherited' }
+        },
+        {
+          match: [/\b/, APEX_IDENT_RE, /\./, APEX_IDENT_RE, /\b/],
+          scope: { 2: 'title.class.inherited', 4: 'title.class.inherited' }
+        }
+      ]
     }
   ];
 
-  const MERGE_FIELDS = {
+  const DML_SECURITY = {
+    // TODO: Pick better scopes here
+    match: ['AccessLevel', '.', /(SYSTEM_MODE|USER_MODE)/],
+    scope: {
+      1: 'built_in',
+      3: 'keyword'
+    }
+  };
+
+  /* const MERGE_FIELDS = {
     begin: ['{', /\$[a-zA-Z]+]/, '.', /\w+/],
     end: '}',
-    scope: {
+    beginScope: {
+      1: 'punctuation',
       2: 'built_in',
       4: 'property'
-    }
-  };
+    },
+    endScope: 'punctuation'
+  }; */
 
-  const FOR_LOOP = {
-    variants: [
-      {
-        //for (APTask__c apTask : myTasks
-        match: [
-          /\bfor\b/,
-          /\s*\(/,
-          APEX_IDENT_RE,
-          /\s+/,
-          APEX_IDENT_RE,
-          /\s+:/,
-          /(?=\s*\[)/
-        ],
-        scope: {
-          1: 'keyword',
-          3: 'type'
-          //5: 'variable'
-        }
-      },
-      {
-        match: [
-          /\bfor\b/,
-          /\s*\(/,
-          APEX_IDENT_RE,
-          /\s+/,
-          APEX_IDENT_RE,
-          /\s+:/,
-          /\s*/,
-          APEX_IDENT_RE
-        ],
-        scope: {
-          1: 'keyword',
-          3: 'type',
-          //5: 'variable',
-          8: 'variable'
-        }
-      }
-    ],
-    contains: [COMMENT_LINE, COMMENT_BLOCK, SOQL_QUERY]
-  };
 
   const ASSIGNMENTS = [
     {
+      // Account a =
       match: [APEX_IDENT_RE, /\s+/, APEX_IDENT_RE, /\s+/, /=/],
       scope: {
         1: 'type',
@@ -723,6 +811,7 @@ export default function (hljs) {
       relevance: 0
     },
     {
+      // Account abcd;
       match: [APEX_IDENT_RE, /\s+/, APEX_IDENT_RE, /\s+/, ';'],
       scope: {
         1: 'type',
@@ -731,19 +820,38 @@ export default function (hljs) {
       relevance: 0
     },
     {
+      // mynum =
       match: [/\s+/, APEX_IDENT_RE, /\s+/, /=/],
       scope: {
         2: 'variable',
         4: 'operator'
       },
       relevance: 0
-    },
+    } /* ,
     {
+      // TODO: Figure out what this was doing, and remove the non-fixed width lookbehind
       match: [/(?<=\w+\s+=\s+\()/, APEX_IDENT_RE, /(?=\))/],
       scope: {
         2: 'type'
       },
       relevance: 0
+    } */
+  ];
+
+  const RETURNS = [
+    {
+      match: [/(?<=return)/, /\s+/, APEX_IDENT_RE, /(?=\s*;)/],
+      scope: {
+        3: 'variable'
+      }
+    },
+    {
+      begin: [/(?<=return)/, /\s+/, APEX_IDENT_RE, /\(/],
+      end: /\)/,
+      beginScope: {
+        3: 'title.function.invoke'
+      },
+      contains: [PARAM_VARS]
     }
   ];
 
@@ -753,6 +861,246 @@ export default function (hljs) {
     relevance: 8
   };
 
+  /**
+   * SOQL SECTION
+   */
+
+  const SOQL_KEYWORDS = [
+    'ABOVE_OR_BELOW',
+    'ACTIVE',
+    'ADVANCED',
+    'ALL',
+    'ANY',
+    'ARRAY',
+    'AS',
+    'BY',
+    'CATEGORY',
+    'CONTAINS',
+    'CUSTOM',
+    'DATA',
+    'DIVISION',
+    'END',
+    'FIELDS',
+    'FIND|10',
+    'LAST',
+    'METADATA',
+    'NETWORK',
+    'ON',
+    'RETURNING',
+    'ROLLUP',
+    'ROWS',
+    'SEARCH',
+    'SECURITY_ENFORCED',
+    'SELECT',
+    'SNIPPET',
+    'SORT',
+    'STANDARD',
+    'USER_MODE',
+    'WHERE'
+  ];
+
+  const SOQL_OPERATORS = [
+    ':',
+    'ABOVE',
+    'AND',
+    'ASC',
+    'AT',
+    'DESC',
+    'DISTANCE',
+    'FROM',
+    'HAVING',
+    'IN',
+    'LIKE',
+    'LIMIT',
+    'LISTVIEW',
+    'NOT',
+    'OFFSET',
+    'OR',
+    'TRACKING',
+    'TYPEOF',
+    'UPDATE',
+    'USING',
+    'VIEWSTAT',
+    'YESTERDAY',
+    /FOR\s+REFERENCE/,
+    /FOR\s+UPDATE/,
+    /FOR\s+VIEW/,
+    /GROUP\s+BY/,
+    /NOT\s+IN/,
+    /NULLS\s+FIRST/,
+    /NULLS\s+LAST/,
+    /ORDER\s+BY/,
+    //WITH/,
+    /WITH\s+DATA\s+CATEGORY/,
+    /WITH\s+PricebookId/
+  ];
+
+  const SOQL_FUNCTIONS = [
+    'AVG',
+    'convertCurrency',
+    'convertTimezone',
+    'COUNT',
+    'COUNT_DISTINCT',
+    'DAY_IN_MONTH',
+    'DAY_IN_WEEK',
+    'DAY_IN_YEAR',
+    'DAY_ONLY',
+    'toLabel',
+    'INCLUDES',
+    'EXCLUDES',
+    'FORMAT',
+    'GROUPING',
+    'GROUP BY CUBE',
+    'GROUP BY ROLLUP',
+    'HOUR_IN_DAY',
+    'MAX',
+    'MIN',
+    'SUM',
+    'WEEK_IN_MONTH',
+    'WEEK_IN_YEAR'
+  ];
+
+  const SOQL_DATES = [
+    'CALENDAR_MONTH',
+    'CALENDAR_QUARTER',
+    'CALENDAR_YEAR',
+    'FISCAL_MONTH',
+    'FISCAL_QUARTER',
+    'FISCAL_YEAR',
+    'TODAY',
+    'TOMORROW',
+  ]
+
+  const SOQL_SCOPE = [
+    'Delegated',
+    'Everything',
+    'Mine',
+    'My_Territory',
+    'My_Team_Territory',
+    'Team'
+  ];
+
+  const SOQL_QUERY = {
+    begin: /\[\s*\b(SELECT|FIND)\b/,
+    end: /\]/,
+    scope: 'soql',
+    relevance: 10,
+    contains: [
+      {
+        begin: regex.concat(/\b/, regex.either(...SOQL_KEYWORDS), /\b/),
+        scope: 'keyword'
+      },
+      {
+        match: regex.concat(/\b/, regex.either(...SOQL_OPERATORS), /\b/),
+        scope: 'operator',
+        relevance: 0
+      },
+      {
+        match: regex.concat(/\b/, regex.either(...SOQL_FUNCTIONS), /\b/),
+        scope: 'title.function.invoke'
+      },
+      {
+        match: /(NEXT|LAST|THIS)_(N_)?(90_DAY|DAY|FISCAL_QUARTER|FISCAL_YEAR|MONTH|QUARTER|WEEK|YEAR)S?/,
+        scope: 'literal'
+      },
+      {
+        match: regex.concat(/\b/, regex.either(...SOQL_DATES), /\b/),
+        scope: 'literal'
+      },
+      //{
+      //   match: /(\bIN\b|<|<=|>|>=|\bNOT\s+IN\b|=|!\s*=|\s:{1}|:{1}\s)/,
+      //   scope: 'literal'
+      //},
+      /* {
+        match: [/(?<=\bFROM\b\s+)/, APEX_IDENT_RE, /\./, APEX_IDENT_RE],
+        scope: { 2: 'type', 4: 'type' },
+        relevance: 0
+      }, */
+      {
+        match: /(?<=\bFROM\b\s+)\w+/,
+        scope: 'type',
+        relevance: 0
+      },
+      {
+        match: [/\bUSING\s+SCOPE\b\s+/, regex.either(...SOQL_SCOPE)],
+        scope: { 1: 'variable.language', 2: 'variable.language' }
+      },
+      {
+        match: [
+          /(NEXT|LAST|THIS)_(N_)?(90_DAY|DAY|FISCAL_QUARTER|FISCAL_YEAR|MONTH|QUARTER|WEEK|YEAR)S?/,
+          /\s*/,
+          ':',
+          /\s*/,
+          /[0-9]+\b/
+        ],
+        scope: {
+          1: 'variable.language',
+          3: 'punctuation',
+          5: 'number'
+        },
+        relevance: 10
+      },{
+      match: [/(?<=:)/, /\s*/, APEX_IDENT_RE],
+      scope: {3: 'variable'}
+      },
+      {
+        match:/[(:)]/,
+        scope: 'punctuation',
+        relevance: 0
+      },
+      NUMBER,
+      METHOD_CALL,
+      hljs.APOS_STRING_MODE,
+      OPERATORS
+    ],
+    illegal: '::'
+  };
+
+  const FOR_LOOP = {
+    // Treat this loop specifically because it can take a query
+    // Leave do/while to regular highlighting
+    variants: [
+      {
+        //for (APTask__c apTask : myTasks
+        match: [
+          /\bfor\b/,
+          /\s*\(/,
+          APEX_IDENT_RE,
+          /\s+/,
+          APEX_IDENT_RE,
+          /\s*:/,
+          /(?=\s*\[)/
+        ],
+        scope: {
+          1: 'keyword',
+          3: 'type',
+          5: 'variable',
+          6: 'punctuation'
+        }
+      },
+      {
+        match: [
+          /\bfor\b/, //1 for
+          /\s*\(/, //2 (
+          APEX_IDENT_RE, //3 Account
+          /\s+/,
+          APEX_IDENT_RE, //5 a
+          /\s*:/, //6 :
+          /\s*/,
+          APEX_IDENT_RE //8 myList
+        ],
+        scope: {
+          1: 'keyword',
+          2: 'punctuation',
+          3: 'type',
+          5: 'variable',
+          8: 'variable'
+        }
+      }
+    ],
+    contains: [COMMENT_LINE, COMMENT_BLOCK, SOQL_QUERY]
+  };
+
   const ILLEGALS = [
     '/,
+    /<%/,
+    '<%#',
+    '%%>',
+    '<%%'
     // /"[^"]+"/, // Quote_string_mode
     // /@\w+\[\w+\]/ //moonscript
   ];
 
+  const KEYWORDS = {
+    $pattern: APEX_IDENT_RE,
+    keyword: MAIN_KEYWORDS,
+    'variable.language': LANGUAGE_VARS,
+    built_in: [...BUILT_INS, ...DMLS, ...NAMESPACE_LIST],
+    type: TYPES,
+    literal: LITERALS,
+    classNameAliases: {
+      soql: 'subst'
+    }
+  };
+
   return {
     name: 'Apex',
     aliases: ['apex', 'lightning'],
@@ -817,21 +1183,26 @@ export default function (hljs) {
       ASSIGNMENTS,
       CLASS_SHARING,
       COLLECTION_DECLARATION,
-      COLLECTION_MAP_VALUE,
+      COLLECTION_VALUE,
       COMMENT_BLOCK,
       COMMENT_LINE,
       COMPOUND_KEYWORDS,
-      //CUSTOM_OBJECT,
+      CUSTOM_METADATA,
+      DML_SECURITY,
       EXCEPTION,
       FOR_LOOP,
       hljs.APOS_STRING_MODE,
+      INSTANTIATE,
       METHOD_CALL,
-      MERGE_FIELDS,
+      //MERGE_FIELDS,
       NAMESPACES,
       NUMBER,
       OPERATORS,
+      PUNCTUATION,
+      RETURNS,
       SALESFORCE_ID,
       SOQL_QUERY,
+      SYSTEM_INVOKE,
       //ANNOTATION_ATTRIBUTE_PARAMS,
       IMPORTANT_WORDS
     ]
diff --git a/test/markup/apex/apexcode.expected.txt b/test/markup/apex/apexcode.expected.txt
index 0959558..76e7b1f 100644
--- a/test/markup/apex/apexcode.expected.txt
+++ b/test/markup/apex/apexcode.expected.txt
@@ -1,50 +1,50 @@
 /**
  * @author John Smith
  */
-@IsTest(Seealldata=true)
-public with sharing class L2Char implements Database.batchable {
+@IsTest(Seealldata=true)
+public with sharing class L2Char implements Database.batchable {
   public static final String ERROR = 0x0001;
 
-  @InvocableApex(label='my invocable')
+  @InvocableApex(label='my invocable')
   public void moveTo(
-    integer x, 
-    integer y, 
-    integer z
+    integer x, 
+    integer y, 
+    integer z
   ) {
     
     
-    Account a = new Account();
+    Account a = new Account();
     a.Custom__c = 'stringvalue';
     insert a;
     Boolean ai = (Boolean) false;
-    System.debug('Should not be called');
-    if (1 > 5) { // wtf!?
-      Database.insert(myAccounts);
+    System.debug('Should not be called');
+    if (1 > 5) { // wtf!?
+      Database.insert(myAccounts);
     }
   }
 }
 
 @TestSetup
-private static void makeData(Boolean a){
-  Custom__c c = new Custom__c();
+private static void makeData(Boolean a){
+  Custom__c c = new Custom__c();
   
-  for(Account a : acctLis ){
-    ConnectApi.insert a;
+  for(Account a : acctLis ){
+    ConnectApi.insert a;
   }
 }
 
 private testMethod void testme(){
-  System.assert(true);
+  System.assert(true);
 }
 
 @testVisible
 private List<SelectOption> recordTypes { get; private set; }
 
-for(Account a : [SELECT Id FROM Account WHERE LastModifiedDate = LAST_N_DAYS:3]){
-  Assert.fail();
+for(Account a : [SELECT Id FROM Account WHERE LastModifiedDate = LAST_N_DAYS:3]){
+  Assert.fail();
 }
 
-trigger CTrig on Custom__c (before insert){
-  System.debug('inserting a record');
-  upsert myRecord__c;
+trigger CTrig on Custom__c (before insert){
+  System.debug('inserting a record');
+  upsert myRecord__c;
 }
\ No newline at end of file