From 42c796c0b918bfd36318379159fd4eea8a546f47 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 13 Mar 2026 22:53:35 +0000 Subject: [PATCH 1/4] Merge apex-types repository into outline-parser This commit merges the apex-types library (v1.3.0) into outline-parser to simplify the dependency chain and enable future improvements to diagnostic position reporting. Changes: - Added all apex-types source files (18 Scala, 4 Java, 3 JS-specific) - com.financialforce.types.* (type interfaces) - com.financialforce.types.base.* (base types like Modifier, Annotation, Location) - io.github.apexdevtools.api.* (Issue API for JVM/JS) - io.github.apexdevtools.spi.* (AnalysisProvider SPI for JVM) - Removed apex-types dependency from build.sbt - Updated project description to reflect bundled type definitions - Updated README.md to document the merge All 6,302 existing tests pass successfully. This is a foundation for addressing diagnostic position issues reported in apex-dev-tools/apex-ls#434. Resolves #18 --- README.md | 2 + build.sbt | 3 +- .../io/github/apexdevtools/api/Issue.scala | 47 +++++ .../apexdevtools/api/IssueLocation.scala | 54 ++++++ .../io/github/apexdevtools/api/Rule.scala | 34 ++++ .../io/github/apexdevtools/api/Issue.java | 67 +++++++ .../apexdevtools/api/IssueLocation.java | 58 ++++++ .../java/io/github/apexdevtools/api/Rule.java | 55 ++++++ .../apexdevtools/spi/AnalysisProvider.java | 44 +++++ .../types/IBodyDeclaration.scala | 15 ++ .../types/IConstructorDeclaration.scala | 46 +++++ .../types/IFieldDeclaration.scala | 21 +++ .../types/IFormalParameter.scala | 25 +++ .../financialforce/types/IInitializer.scala | 18 ++ .../types/IMethodDeclaration.scala | 53 ++++++ .../types/IPropertyDeclaration.scala | 22 +++ .../types/ITypeDeclaration.scala | 53 ++++++ .../com/financialforce/types/IVariable.scala | 38 ++++ .../types/base/Annotation.scala | 36 ++++ .../types/base/AnnotationsAndModifiers.scala | 12 ++ .../com/financialforce/types/base/Id.scala | 46 +++++ .../financialforce/types/base/Location.scala | 57 ++++++ .../financialforce/types/base/Modifier.scala | 31 ++++ .../types/base/PropertyBlock.scala | 7 + .../types/base/QualifiedName.scala | 22 +++ .../financialforce/types/base/TypeRef.scala | 165 ++++++++++++++++++ .../com/financialforce/types/package.scala | 29 +++ 27 files changed, 1058 insertions(+), 2 deletions(-) create mode 100644 js/src/main/scala/io/github/apexdevtools/api/Issue.scala create mode 100644 js/src/main/scala/io/github/apexdevtools/api/IssueLocation.scala create mode 100644 js/src/main/scala/io/github/apexdevtools/api/Rule.scala create mode 100644 jvm/src/main/java/io/github/apexdevtools/api/Issue.java create mode 100644 jvm/src/main/java/io/github/apexdevtools/api/IssueLocation.java create mode 100644 jvm/src/main/java/io/github/apexdevtools/api/Rule.java create mode 100644 jvm/src/main/java/io/github/apexdevtools/spi/AnalysisProvider.java create mode 100644 shared/src/main/scala/com/financialforce/types/IBodyDeclaration.scala create mode 100644 shared/src/main/scala/com/financialforce/types/IConstructorDeclaration.scala create mode 100644 shared/src/main/scala/com/financialforce/types/IFieldDeclaration.scala create mode 100644 shared/src/main/scala/com/financialforce/types/IFormalParameter.scala create mode 100644 shared/src/main/scala/com/financialforce/types/IInitializer.scala create mode 100644 shared/src/main/scala/com/financialforce/types/IMethodDeclaration.scala create mode 100644 shared/src/main/scala/com/financialforce/types/IPropertyDeclaration.scala create mode 100644 shared/src/main/scala/com/financialforce/types/ITypeDeclaration.scala create mode 100644 shared/src/main/scala/com/financialforce/types/IVariable.scala create mode 100644 shared/src/main/scala/com/financialforce/types/base/Annotation.scala create mode 100644 shared/src/main/scala/com/financialforce/types/base/AnnotationsAndModifiers.scala create mode 100644 shared/src/main/scala/com/financialforce/types/base/Id.scala create mode 100644 shared/src/main/scala/com/financialforce/types/base/Location.scala create mode 100644 shared/src/main/scala/com/financialforce/types/base/Modifier.scala create mode 100644 shared/src/main/scala/com/financialforce/types/base/PropertyBlock.scala create mode 100644 shared/src/main/scala/com/financialforce/types/base/QualifiedName.scala create mode 100644 shared/src/main/scala/com/financialforce/types/base/TypeRef.scala create mode 100644 shared/src/main/scala/com/financialforce/types/package.scala diff --git a/README.md b/README.md index dc755b0..c89db1b 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ This parser extracts an outline from Apex class files. The outline provides structural information about the class and its inner classes without needing to parse code blocks. The performance of this parser is much better than a full parser, making it ideal for use when indexing or similar activities. +This library includes the Apex type definitions (interfaces and base types) that were previously published separately as `apex-types`. Starting with version 2.0.0, these are bundled together to simplify dependency management. + If you need access to a full syntax tree for Apex, SOQL or SOSL we recommend using the [apex-parser](https://github.com/apex-dev-tools/apex-parser) instead. ## Getting Started diff --git a/build.sbt b/build.sbt index a44d512..32095cb 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ import org.scalajs.linker.interface.Report import scala.sys.process._ ThisBuild / scalaVersion := "2.13.10" -ThisBuild / description := "Salesforce Apex outline parser" +ThisBuild / description := "Salesforce Apex outline parser with type definitions" ThisBuild / organization := "io.github.apex-dev-tools" ThisBuild / organizationHomepage := Some(url("https://github.com/apex-dev-tools/outline-parser")) ThisBuild / homepage := Some(url("https://github.com/apex-dev-tools/outline-parser")) @@ -42,7 +42,6 @@ lazy val parser = crossProject(JSPlatform, JVMPlatform) name := "outline-parser", scalacOptions += "-deprecation", libraryDependencies ++= Seq( - "io.github.apex-dev-tools" %%% "apex-types" % "1.3.0", "org.scalatest" %%% "scalatest" % "3.2.9" % Test, "io.github.apex-dev-tools" %%% "apex-ls" % "4.3.1" % Test ) diff --git a/js/src/main/scala/io/github/apexdevtools/api/Issue.scala b/js/src/main/scala/io/github/apexdevtools/api/Issue.scala new file mode 100644 index 0000000..5f7f5ef --- /dev/null +++ b/js/src/main/scala/io/github/apexdevtools/api/Issue.scala @@ -0,0 +1,47 @@ +/* + Copyright (c) 2021 Kevin Jones, All rights reserved. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. 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. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + */ + +package io.github.apexdevtools.api + +/* WARNING: This must be identical to Java class of same name */ +trait Issue { + + /* The generator of this issue, default to APEX_LS_PROVIDER */ + def provider(): String + + /* The file path where the issue was found */ + def filePath(): String + + /* The location within the file */ + def fileLocation(): IssueLocation + + /* The rule violated, sets the issue priority */ + def rule(): Rule + + /* The issue message */ + def message(): String + + /* Is this considered an error issue, rather than a warning */ + def isError: java.lang.Boolean + + /* Format as String, filePath is omitted to avoid duplicating over multiple Issues */ + def asString: String = rule().name() + ": " + fileLocation().displayPosition + ": " + message() + + override def toString: String = filePath() + ": " + asString +} + +object Issue { + // Default source name for apex-ls generated diagnostics + final val APEX_LS_PROVIDER = "apex-ls" +} diff --git a/js/src/main/scala/io/github/apexdevtools/api/IssueLocation.scala b/js/src/main/scala/io/github/apexdevtools/api/IssueLocation.scala new file mode 100644 index 0000000..611311e --- /dev/null +++ b/js/src/main/scala/io/github/apexdevtools/api/IssueLocation.scala @@ -0,0 +1,54 @@ +/* + Copyright (c) 2021 Kevin Jones, All rights reserved. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. 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. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + */ + +package io.github.apexdevtools.api + +/* WARNING: This must be identical to Java class of same name */ +trait IssueLocation { + def startLineNumber(): Int + def startCharOffset(): Int + def endLineNumber(): Int + def endCharOffset(): Int + + def displayPosition: String = { + if ( + startLineNumber() == 1 && endLineNumber() == Int.MaxValue && startCharOffset() == 0 && endCharOffset() == 0 + ) { + s"line 1" + } else if (startLineNumber() == endLineNumber()) { + if (startCharOffset() == 0 && endCharOffset() == 0) + s"line ${startLineNumber()}" + else if (startCharOffset() == endCharOffset()) + s"line ${startLineNumber()} at ${startCharOffset()}" + else + s"line ${startLineNumber()} at ${startCharOffset()}-${endCharOffset()}" + } else { + if (startCharOffset() == 0 && endCharOffset() == 0) + s"line ${startLineNumber()} to ${endLineNumber()}" + else + s"line ${startLineNumber()}:${startCharOffset()} to ${endLineNumber()}:${endCharOffset()}" + } + } + + def contains(line: Int, offset: Int): Boolean = { + !(line < startLineNumber() || line > endLineNumber() || + (line == startLineNumber() && offset < startCharOffset()) || + (line == endLineNumber() && offset > endCharOffset())) + } + + def contains(other: IssueLocation): Boolean = { + contains(other.startLineNumber(), other.startCharOffset()) && + contains(other.endLineNumber(), other.endCharOffset()) + } +} diff --git a/js/src/main/scala/io/github/apexdevtools/api/Rule.scala b/js/src/main/scala/io/github/apexdevtools/api/Rule.scala new file mode 100644 index 0000000..9b33b2c --- /dev/null +++ b/js/src/main/scala/io/github/apexdevtools/api/Rule.scala @@ -0,0 +1,34 @@ +/* + Copyright (c) 2021 Kevin Jones, All rights reserved. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. 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. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + */ + +package io.github.apexdevtools.api + +/* WARNING: This must be identical to Java class of same name */ +trait Rule { + /* The nane of the rule */ + def name(): String + + /* Range 1-5, 1 being the highest */ + def priority(): Integer +} + +object Rule { + // Priority constants, these should become enums after moving to Scala3 + // These are based on SonarQube, PMD uses Integer 1-5 + final val BLOCKER_PRIORITY = 1 // Change absolutely required + final val CRITICAL_PRIORITY = 2 // Change highly recommended + final val MAJOR_PRIORITY = 3 // Change recommended + final val MINOR_PRIORITY = 4 // Change optional + final val INFO_PRIORITY = 5 // Change highly optional +} diff --git a/jvm/src/main/java/io/github/apexdevtools/api/Issue.java b/jvm/src/main/java/io/github/apexdevtools/api/Issue.java new file mode 100644 index 0000000..39164b8 --- /dev/null +++ b/jvm/src/main/java/io/github/apexdevtools/api/Issue.java @@ -0,0 +1,67 @@ +/* + Copyright (c) 2021 Kevin Jones, All rights reserved. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. 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. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + */ + +package io.github.apexdevtools.api; + +/** + * A diagnostic issue. + * Each issue must identify which provider generated it, the file path & location it refers to, + * a Rule the issue relates to, if the issue should be considered an error and message for + * the issue. This is based on the PMD model of a diagnostic issue. + * WARNING: This must be identical to Scala class of same name + */ +public abstract class Issue { + + /** + * Name of provider which created this Issue + */ + public abstract String provider(); + + /** + * The file path where the issue was found + */ + public abstract String filePath(); + + /** + * The location within the file + */ + public abstract IssueLocation fileLocation(); + + /** + * The rule violated, sets the issue priority + */ + public abstract Rule rule(); + + /** + * Is this considered an error issue, rather than a warning + */ + public abstract Boolean isError(); + + /** + * The issue message + */ + public abstract String message(); + + /** + * Format as String, filePath is omitted to avoid duplicating over multiple Issues + */ + public String asString() { + return rule().name() + ": " + fileLocation().displayPosition() + ": " + message(); + } + + @Override + public String toString() { + return filePath() + ": " + asString(); + } +} diff --git a/jvm/src/main/java/io/github/apexdevtools/api/IssueLocation.java b/jvm/src/main/java/io/github/apexdevtools/api/IssueLocation.java new file mode 100644 index 0000000..63657fc --- /dev/null +++ b/jvm/src/main/java/io/github/apexdevtools/api/IssueLocation.java @@ -0,0 +1,58 @@ +/* + Copyright (c) 2021 Kevin Jones, All rights reserved. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. 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. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + */ + +package io.github.apexdevtools.api; + +/** + * Location of an Issue in a file. + * Line numbers start at 1, character offsets within a line start at 0. + * Assumes start position <= end position but does not validate this. + * WARNING: This must be identical to Scala class of same name + */ +public abstract class IssueLocation { + public abstract int startLineNumber(); + public abstract int startCharOffset(); + public abstract int endLineNumber(); + public abstract int endCharOffset(); + + public String displayPosition() { + if (startLineNumber() == 1 && endLineNumber() == Integer.MAX_VALUE && startCharOffset() == 0 && endCharOffset() == 0) { + return "line 1"; + } + if (startLineNumber() == endLineNumber()) { + if (startCharOffset() == 0 && endCharOffset() == 0) + return "line " + startLineNumber(); + else if (startCharOffset() == endCharOffset()) + return "line " + startLineNumber() + " at " + startCharOffset(); + else + return "line " + startLineNumber() + " at " + startCharOffset() + "-" + endCharOffset(); + } else { + if (startCharOffset() == 0 && endCharOffset() == 0) + return "line " + startLineNumber() + " to " + endLineNumber(); + else + return "line " + startLineNumber() + ":" + startCharOffset() + " to " + endLineNumber() + ":" + endCharOffset(); + } + } + + public boolean contains(int line, int offset) { + return !(line < startLineNumber() || line > endLineNumber() || + (line == startLineNumber() && offset < startCharOffset()) || + (line == endLineNumber() && offset > endCharOffset())); + } + + public boolean contains(IssueLocation other) { + return contains(other.startLineNumber(), other.startCharOffset()) && + contains(other.endLineNumber(), other.endCharOffset()); + } +} diff --git a/jvm/src/main/java/io/github/apexdevtools/api/Rule.java b/jvm/src/main/java/io/github/apexdevtools/api/Rule.java new file mode 100644 index 0000000..0c9d312 --- /dev/null +++ b/jvm/src/main/java/io/github/apexdevtools/api/Rule.java @@ -0,0 +1,55 @@ +/* + Copyright (c) 2021 Kevin Jones, All rights reserved. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. 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. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + */ + +package io.github.apexdevtools.api; + +/** + * Analysis named rule. + * Provides basic information about the Rule. + * WARNING: This must be identical to Scala class of same name + */ +public interface Rule { + // Priority constants, these are based on SonarQube, PMD uses Integer 1-5 + + /** + * Change absolutely required + */ + Integer BLOCKER_PRIORITY = 1; + /** + * Change highly recommended + */ + Integer CRITICAL_PRIORITY = 2; + /** + * Change recommended + */ + Integer MAJOR_PRIORITY = 3; + /** + * Change optional + */ + Integer MINOR_PRIORITY = 4; + /** + * Change highly optional + */ + Integer INFO_PRIORITY = 5; + + /** + * The nane of the rule, does need to be unique but recommended it is + */ + String name(); + + /** + * Priority Range 1-5, 1 being the highest + */ + Integer priority(); +} diff --git a/jvm/src/main/java/io/github/apexdevtools/spi/AnalysisProvider.java b/jvm/src/main/java/io/github/apexdevtools/spi/AnalysisProvider.java new file mode 100644 index 0000000..f81381e --- /dev/null +++ b/jvm/src/main/java/io/github/apexdevtools/spi/AnalysisProvider.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 FinancialForce.com, inc. All rights reserved. + */ +package io.github.apexdevtools.spi; + +import io.github.apexdevtools.api.Issue; + +import java.nio.file.Path; +import java.util.List; + +/** + * Service provider for an external analysis that can return Issues. + * The Provider may throw on bad input. + */ +public interface AnalysisProvider { + + /** + * Return an identifier for the provider, these need to be unique across all providers. + */ + String getProviderId(); + + /** + * Set provider configuration items, these need to fit a key->value(s) model. + * + * @param name name of configuration parameter + * @param values optional list of values to use with parameter + */ + void setConfiguration(String name, List values); + + /** + * Test if configured correctly for use in workspace + * + * @param workspacePath workspace to run analysis in + */ + Boolean isConfigured(Path workspacePath); + + /** + * Return issues for the set of files. + * + * @param workspacePath workspace to run analysis in + * @param files files within workspace to analyse + */ + Issue[] collectIssues(Path workspacePath, Path[] files); +} diff --git a/shared/src/main/scala/com/financialforce/types/IBodyDeclaration.scala b/shared/src/main/scala/com/financialforce/types/IBodyDeclaration.scala new file mode 100644 index 0000000..4099da9 --- /dev/null +++ b/shared/src/main/scala/com/financialforce/types/IBodyDeclaration.scala @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2021 FinancialForce.com, inc. All rights reserved. + */ +package com.financialforce.types + +import com.financialforce.types.base.{IdWithLocation, Location} + +/** Common handling for elements that can appear in a class body. bodyLocation define the location of the element + * while blockLocation defines the location of any nested block which will be a subpart of the bodyLocation. + */ +trait IBodyDeclaration { + def id: IdWithLocation + def bodyLocation: Option[Location] + def blockLocation: Option[Location] +} diff --git a/shared/src/main/scala/com/financialforce/types/IConstructorDeclaration.scala b/shared/src/main/scala/com/financialforce/types/IConstructorDeclaration.scala new file mode 100644 index 0000000..e67a21d --- /dev/null +++ b/shared/src/main/scala/com/financialforce/types/IConstructorDeclaration.scala @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 FinancialForce.com, inc. All rights reserved. + */ +package com.financialforce.types + +import com.financialforce.types.base._ + +import scala.collection.immutable.ArraySeq +import scala.util.hashing.MurmurHash3 + +/** Class constructor, note custom equality does not include location information. */ +trait IConstructorDeclaration extends IBodyDeclaration with AnnotationsAndModifiers { + def qname: QualifiedName + def formalParameters: ArraySeq[IFormalParameter] + override def id: IdWithLocation + override def bodyLocation: Option[Location] + override def blockLocation: Option[Location] + override def annotations: Array[Annotation] + override def modifiers: Array[Modifier] + + def signature: String = + s"$annotationsAndModifiers $qname(${formalParameters.mkString(", ")})".tidyWhitespace + + override def equals(obj: Any): Boolean = { + val other = obj.asInstanceOf[IConstructorDeclaration] + (other.annotations sameElements annotations) && + (other.modifiers sameElements modifiers) && + other.id == id && + other.formalParameters == formalParameters + } + + override def hashCode(): Int = { + MurmurHash3.orderedHash( + Seq( + MurmurHash3.arrayHash(annotations), + MurmurHash3.arrayHash(modifiers), + id, + formalParameters + ) + ) + } + + override def toString: String = { + s"${id.location} $signature".tidyWhitespace + } +} diff --git a/shared/src/main/scala/com/financialforce/types/IFieldDeclaration.scala b/shared/src/main/scala/com/financialforce/types/IFieldDeclaration.scala new file mode 100644 index 0000000..7d3f338 --- /dev/null +++ b/shared/src/main/scala/com/financialforce/types/IFieldDeclaration.scala @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2021 FinancialForce.com, inc. All rights reserved. + */ +package com.financialforce.types + +import com.financialforce.types.base._ + +/** Class field, note custom IVariable equality does not include location information or property blocks. */ +trait IFieldDeclaration extends IBodyDeclaration with IVariable { + override var typeRef: TypeRef + override def id: IdWithLocation + override def bodyLocation: Option[Location] + override def blockLocation: Option[Location] + override def annotations: Array[Annotation] + override def modifiers: Array[Modifier] + + override def equals(obj: Any): Boolean = { + val other = obj.asInstanceOf[IFieldDeclaration] + super.equals(other) + } +} diff --git a/shared/src/main/scala/com/financialforce/types/IFormalParameter.scala b/shared/src/main/scala/com/financialforce/types/IFormalParameter.scala new file mode 100644 index 0000000..071a607 --- /dev/null +++ b/shared/src/main/scala/com/financialforce/types/IFormalParameter.scala @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2021 FinancialForce.com, inc. All rights reserved. + */ +package com.financialforce.types + +import com.financialforce.types.base._ + +/* + * Copyright (c) 2021 FinancialForce.com, inc. All rights reserved. + */ + +/** Method formal parameter, tightly aligned to IVariable. */ +trait IFormalParameter extends IVariable with IdWithLocation { + override def location: Location + override def annotations: Array[Annotation] + override def modifiers: Array[Modifier] + override var typeRef: TypeRef + override def name: String + override def id = new LocatableId(name, location) + + override def equals(obj: Any): Boolean = { + val other = obj.asInstanceOf[IFormalParameter] + super.equals(other) + } +} diff --git a/shared/src/main/scala/com/financialforce/types/IInitializer.scala b/shared/src/main/scala/com/financialforce/types/IInitializer.scala new file mode 100644 index 0000000..17d7fe8 --- /dev/null +++ b/shared/src/main/scala/com/financialforce/types/IInitializer.scala @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2021 FinancialForce.com, inc. All rights reserved. + */ +package com.financialforce.types + +import com.financialforce.types.base.{IdWithLocation, Location} + +/* + * Copyright (c) 2021 FinancialForce.com, inc. All rights reserved. + */ + +/** Static or non-static initializer block */ +trait IInitializer extends IBodyDeclaration { + def isStatic: Boolean + override def id: IdWithLocation + override def bodyLocation: Option[Location] + override def blockLocation: Option[Location] +} diff --git a/shared/src/main/scala/com/financialforce/types/IMethodDeclaration.scala b/shared/src/main/scala/com/financialforce/types/IMethodDeclaration.scala new file mode 100644 index 0000000..1ce0b7a --- /dev/null +++ b/shared/src/main/scala/com/financialforce/types/IMethodDeclaration.scala @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 FinancialForce.com, inc. All rights reserved. + */ +package com.financialforce.types + +import com.financialforce.types.base._ + +import scala.collection.immutable.ArraySeq +import scala.util.hashing.MurmurHash3 + +/** Class method, note custom equality does not include location information. */ +trait IMethodDeclaration extends IBodyDeclaration with AnnotationsAndModifiers { + var typeRef: Option[TypeRef] + def formalParameters: ArraySeq[IFormalParameter] + override def id: IdWithLocation + override def bodyLocation: Option[Location] + override def blockLocation: Option[Location] + override def annotations: Array[Annotation] + override def modifiers: Array[Modifier] + + def signature: String = { + val typeName = typeRef.map(_.toString).getOrElse("void") + s"$annotationsAndModifiers $typeName $id(${formalParameters.mkString(", ")})".tidyWhitespace + } + + override def equals(obj: Any): Boolean = { + val other = obj.asInstanceOf[IMethodDeclaration] + (other.annotations sameElements annotations) && + (other.modifiers sameElements modifiers) && ( + (other.typeRef.isEmpty && typeRef.isEmpty) || + (other.typeRef.nonEmpty && typeRef.nonEmpty && + other.typeRef.get.sameRef(typeRef.get)) + ) && + other.id == id && + other.formalParameters == formalParameters + } + + override def hashCode(): Int = { + MurmurHash3.orderedHash( + Seq( + MurmurHash3.arrayHash(annotations), + MurmurHash3.arrayHash(modifiers), + typeRef.map(_.toString.toLowerCase().hashCode).getOrElse(0), + id, + formalParameters.hashCode() + ) + ) + } + + override def toString: String = { + s"${id.location} $signature" + } +} diff --git a/shared/src/main/scala/com/financialforce/types/IPropertyDeclaration.scala b/shared/src/main/scala/com/financialforce/types/IPropertyDeclaration.scala new file mode 100644 index 0000000..eab664b --- /dev/null +++ b/shared/src/main/scala/com/financialforce/types/IPropertyDeclaration.scala @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021 FinancialForce.com, inc. All rights reserved. + */ +package com.financialforce.types + +import com.financialforce.types.base._ + +/** Class property, note custom IVariable equality does not include location information or property blocks. */ +trait IPropertyDeclaration extends IBodyDeclaration with IVariable { + def propertyBlocks: Array[PropertyBlock] + override def typeRef: TypeRef + override def id: IdWithLocation + override def bodyLocation: Option[Location] + override def blockLocation: Option[Location] + override def annotations: Array[Annotation] + override def modifiers: Array[Modifier] + + override def equals(obj: Any): Boolean = { + val other = obj.asInstanceOf[IPropertyDeclaration] + super.equals(other) + } +} diff --git a/shared/src/main/scala/com/financialforce/types/ITypeDeclaration.scala b/shared/src/main/scala/com/financialforce/types/ITypeDeclaration.scala new file mode 100644 index 0000000..50de72d --- /dev/null +++ b/shared/src/main/scala/com/financialforce/types/ITypeDeclaration.scala @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 FinancialForce.com, inc. All rights reserved. + */ +package com.financialforce.types + +import com.financialforce.types.base._ + +import scala.collection.immutable.ArraySeq + +/** Class, enum, interface type declaration. May be used as a resolved TypeRef. Uses Identity equality. */ +trait ITypeDeclaration extends TypeRef with AnnotationsAndModifiers { + def paths: Array[String] + def location: Location + + def id: IdWithLocation + + def typeNameSegment: TypeNameSegment + + def enclosing: Option[ITypeDeclaration] + def extendsTypeRef: TypeRef + def implementsTypeList: ArraySeq[TypeRef] + override def modifiers: Array[Modifier] + override def annotations: Array[Annotation] + + def initializers: ArraySeq[IInitializer] + def innerTypes: ArraySeq[ITypeDeclaration] + def constructors: ArraySeq[IConstructorDeclaration] + def methods: ArraySeq[IMethodDeclaration] + def properties: ArraySeq[IPropertyDeclaration] + def fields: ArraySeq[IFieldDeclaration] + + def typeName: Array[TypeNameSegment] = { + enclosing match { + case Some(enc) => Array(enc.typeNameSegment, typeNameSegment) + case None => Array(typeNameSegment) + } + } + + override def fullName: String = { + enclosing.map(_.fullName + ".").getOrElse("") + typeNameSegment.toString + } + + override def toString: String = fullName + + override def equals(that: Any): Boolean = { + that match { + case other: ITypeDeclaration => other.eq(this) + case _ => false + } + } + + override def hashCode(): Int = System.identityHashCode(this) +} diff --git a/shared/src/main/scala/com/financialforce/types/IVariable.scala b/shared/src/main/scala/com/financialforce/types/IVariable.scala new file mode 100644 index 0000000..4ab58c4 --- /dev/null +++ b/shared/src/main/scala/com/financialforce/types/IVariable.scala @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 FinancialForce.com, inc. All rights reserved. + */ +package com.financialforce.types + +import com.financialforce.types.base._ + +import scala.util.hashing.MurmurHash3 + +/** Common handling for Variable like elements (fields, properties and formal parameters) that have similar members. + * Note: custom equality does not include location information. + */ +trait IVariable extends AnnotationsAndModifiers { + var typeRef: TypeRef + def id: IdWithLocation + override def annotations: Array[Annotation] + override def modifiers: Array[Modifier] + + def signature: String = + s"$annotationsAndModifiers $typeRef $id".tidyWhitespace + + override def equals(obj: Any): Boolean = { + val other = obj.asInstanceOf[IVariable] + + (other.annotations sameElements annotations) && + (other.modifiers sameElements modifiers) && + other.typeRef.sameRef(typeRef) && + other.id == id + } + + override def hashCode(): Int = { + MurmurHash3.orderedHash( + Seq(MurmurHash3.arrayHash(annotations), MurmurHash3.arrayHash(modifiers), typeRef, id) + ) + } + + override def toString: String = signature +} diff --git a/shared/src/main/scala/com/financialforce/types/base/Annotation.scala b/shared/src/main/scala/com/financialforce/types/base/Annotation.scala new file mode 100644 index 0000000..11ce25b --- /dev/null +++ b/shared/src/main/scala/com/financialforce/types/base/Annotation.scala @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 FinancialForce.com, inc. All rights reserved. + */ +package com.financialforce.types.base + +import com.financialforce.types.ArrayInternCache + +import scala.util.hashing.MurmurHash3 + +/** Annotation element, name is case-insensitive, parameters are unparsed. */ +case class Annotation(name: String, parameters: Option[String]) { + override def equals(obj: Any): Boolean = { + val other = obj.asInstanceOf[Annotation] + name.equalsIgnoreCase(other.name) && + parameters.getOrElse("").equalsIgnoreCase(other.parameters.getOrElse("")) + } + + override def hashCode(): Int = { + MurmurHash3.orderedHash(Seq(name.toLowerCase(), parameters.getOrElse(""))) + } + + override def toString: String = { + if (parameters.isDefined) s"@$name(${parameters.get})" else s"@$name" + } +} + +/** Caching support for Arrays of annotations. */ +object Annotation { + final val emptyArray = Array[Annotation]() + + private val cache = new ArrayInternCache[Annotation]() + + def intern(annotations: Array[Annotation]): Array[Annotation] = { + cache.intern(annotations) + } +} diff --git a/shared/src/main/scala/com/financialforce/types/base/AnnotationsAndModifiers.scala b/shared/src/main/scala/com/financialforce/types/base/AnnotationsAndModifiers.scala new file mode 100644 index 0000000..9dfd248 --- /dev/null +++ b/shared/src/main/scala/com/financialforce/types/base/AnnotationsAndModifiers.scala @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2021 FinancialForce.com, inc. All rights reserved. + */ +package com.financialforce.types.base + +/** Helper for elements supporting annotations & modifiers */ +trait AnnotationsAndModifiers { + def annotations: Array[Annotation] + def modifiers: Array[Modifier] + + def annotationsAndModifiers: String = (annotations ++ modifiers).mkString(" ") +} diff --git a/shared/src/main/scala/com/financialforce/types/base/Id.scala b/shared/src/main/scala/com/financialforce/types/base/Id.scala new file mode 100644 index 0000000..d6af755 --- /dev/null +++ b/shared/src/main/scala/com/financialforce/types/base/Id.scala @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 FinancialForce.com, inc. All rights reserved. + */ +package com.financialforce.types.base + +/** Identifier, always treat as case insensitive & ideally intern the name. */ +trait Id { + def name: String + + def lowerCaseName: String = name.toLowerCase +} + +/** Identifier with a location in a file from where it was extracted. Location does not hold path information + * so that needs to be available from some other context, such as the ITypeDeclaration that this is found in. + */ +trait IdWithLocation extends Id { + def location: Location +} + +/** Helper for implementing IdWithLocation support into a class in a memory friendly way. */ +abstract class IdLocationHolder(_location: Location) extends IdWithLocation { + // These are inlined to save memory + private val startLine: Int = _location.startLine + private val startLineOffset: Int = _location.startLineOffset + private val startByteOffset: Int = _location.startByteOffset + private val endLine: Int = _location.endLine + private val endLineOffset: Int = _location.endLineOffset + private val endByteOffset: Int = _location.endByteOffset + + override def location: Location = + Location(startLine, startLineOffset, startByteOffset, endLine, endLineOffset, endByteOffset) +} + +/** An Id and its associated location, beware equality is defined only over the id. */ +class LocatableId(override val name: String, _location: Location) + extends IdLocationHolder(_location) { + + override def toString: String = name + + override def equals(obj: Any): Boolean = { + val other = obj.asInstanceOf[LocatableId] + lowerCaseName.equals(other.lowerCaseName) + } + + override val hashCode: Int = lowerCaseName.hashCode +} diff --git a/shared/src/main/scala/com/financialforce/types/base/Location.scala b/shared/src/main/scala/com/financialforce/types/base/Location.scala new file mode 100644 index 0000000..e2c160d --- /dev/null +++ b/shared/src/main/scala/com/financialforce/types/base/Location.scala @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021 FinancialForce.com, inc. All rights reserved. + */ +package com.financialforce.types.base + +/** Position in a source file, uses line/lineOffset & byteOffset to save recomputing later. */ +case class Position(line: Int, lineOffset: Int, byteOffset: Int) { + override def toString: String = { + s"[$line.$lineOffset;$byteOffset" + } +} + +/** Location of a range in a source file, logically a start and end position but unrolled to reduce object overhead. + * Note: We don't enforce constraints such as end>=start so consumers should take care, see also Location.default. + */ +case class Location( + startLine: Int, + startLineOffset: Int, + startByteOffset: Int, + endLine: Int, + endLineOffset: Int, + endByteOffset: Int +) { + def startPosition: Position = Position(startLine, startLineOffset, startByteOffset) + def endPosition: Position = Position(endLine, endLineOffset, endByteOffset) + + override def toString: String = { + s"[$startLine.$startLineOffset->$endLine.$endLineOffset;$startByteOffset->$endByteOffset]" + } +} + +object Location { + /* A default location, maps to start of the file but has zero characters. */ + val default: Location = Location(0, 0, 0, 0, 0, 0) + + def apply(start: Position, end: Position): Location = { + Location( + start.line, + start.lineOffset, + start.byteOffset, + end.line, + end.lineOffset, + end.byteOffset + ) + } + + def span(sl: Location, el: Location): Location = { + Location( + sl.startLine, + sl.startLineOffset, + sl.startByteOffset, + el.endLine, + el.endLineOffset, + el.endByteOffset + ) + } +} diff --git a/shared/src/main/scala/com/financialforce/types/base/Modifier.scala b/shared/src/main/scala/com/financialforce/types/base/Modifier.scala new file mode 100644 index 0000000..ef508ce --- /dev/null +++ b/shared/src/main/scala/com/financialforce/types/base/Modifier.scala @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021 FinancialForce.com, inc. All rights reserved. + */ +package com.financialforce.types.base + +import com.financialforce.types.ArrayInternCache + +/** Modifier element, text is case-insensitive. */ +case class Modifier(text: String) { + override def equals(obj: Any): Boolean = { + val other = obj.asInstanceOf[Modifier] + text.equalsIgnoreCase(other.text) + } + + override def hashCode(): Int = { + text.toLowerCase.hashCode + } + + override def toString: String = text +} + +/** Caching support for Arrays of modifiers. */ +object Modifier { + final val emptyArray = Array[Modifier]() + + private val cache = new ArrayInternCache[Modifier]() + + def intern(modifiers: Array[Modifier]): Array[Modifier] = { + cache.intern(modifiers) + } +} diff --git a/shared/src/main/scala/com/financialforce/types/base/PropertyBlock.scala b/shared/src/main/scala/com/financialforce/types/base/PropertyBlock.scala new file mode 100644 index 0000000..e20beb5 --- /dev/null +++ b/shared/src/main/scala/com/financialforce/types/base/PropertyBlock.scala @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2021 FinancialForce.com, inc. All rights reserved. + */ +package com.financialforce.types.base + +/** Get/Set property block */ +case class PropertyBlock(location: Location) diff --git a/shared/src/main/scala/com/financialforce/types/base/QualifiedName.scala b/shared/src/main/scala/com/financialforce/types/base/QualifiedName.scala new file mode 100644 index 0000000..86ff1e8 --- /dev/null +++ b/shared/src/main/scala/com/financialforce/types/base/QualifiedName.scala @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021 FinancialForce.com, inc. All rights reserved. + */ +package com.financialforce.types.base + +/** Dot seperated name without type arguments. */ +case class QualifiedName(parts: Array[IdWithLocation]) { + def location: Location = { + val start = parts.head.location + val end = parts.last.location + Location.span(start, end) + } + + override def equals(obj: Any): Boolean = { + val other = obj.asInstanceOf[QualifiedName] + parts.sameElements(other.parts) + } + + override def toString: String = { + parts.map(_.toString).mkString(".") + } +} diff --git a/shared/src/main/scala/com/financialforce/types/base/TypeRef.scala b/shared/src/main/scala/com/financialforce/types/base/TypeRef.scala new file mode 100644 index 0000000..f5c5646 --- /dev/null +++ b/shared/src/main/scala/com/financialforce/types/base/TypeRef.scala @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2021 FinancialForce.com, inc. All rights reserved. + */ +package com.financialforce.types.base + +import scala.collection.immutable.ArraySeq +import scala.collection.mutable.ArrayBuffer +import scala.util.hashing.MurmurHash3 + +/** Reference holder to a type. Typically TypeRefs start as a UnresolvedTypeRef and are replaced by a ITypeDeclaration + * during a resolve phase. The fullName can be used to determine equivalence between these two types of TypeRef. + */ +trait TypeRef { + def fullName: String + + def sameRef(other: TypeRef): Boolean = { + other.fullName.equalsIgnoreCase(fullName) + } +} + +object TypeRef { + final val emptyArraySeq: ArraySeq[TypeRef] = ArraySeq() + + def toTypeRefs(params: Array[String]): ArraySeq[TypeRef] = { + ArraySeq.unsafeWrapArray(params.map(tp => { + UnresolvedTypeRef( + Array(new TypeNameSegment(new LocatableId(tp, Location.default), emptyArraySeq)), + 0 + ) + })) + } +} + +/** A reference to a type in raw form as it appears in source files. This just captures the segments in the TypeRef + * and any array subscripts. We expect a resolve process to use this information to locate an ITypeDeclaration. + */ +final case class UnresolvedTypeRef(typeNameSegments: Array[TypeNameSegment], arraySubscripts: Int) + extends TypeRef { + + override def fullName: String = { + typeNameSegments.mkString(".") + ("[]" * arraySubscripts) + } + + override def equals(obj: Any): Boolean = { + val other = obj.asInstanceOf[UnresolvedTypeRef] + typeNameSegments.sameElements(other.typeNameSegments) && + arraySubscripts == other.arraySubscripts + } + + override def hashCode(): Int = { + MurmurHash3.orderedHash(Seq(arraySubscripts, MurmurHash3.arrayHash(typeNameSegments))) + } + + override def toString: String = fullName +} + +object UnresolvedTypeRef { + + /** Convert a string into a UnresolvedTypeRef. Note: This is really only intended for internal use as the + * error handling is limited. + */ + def apply(typeName: String): Either[String, UnresolvedTypeRef] = { + + // Strip trailing array subscripts + var remaining: String = typeName.trim.replaceAll("\\s", "") + var arraySubscripts = 0 + while (remaining.endsWith("[]")) { + arraySubscripts += 1 + remaining = remaining.substring(0, remaining.length - 2) + } + + val parts = safeSplit(remaining, '.') + val segments: List[Either[String, TypeNameSegment]] = parts.map(part => { + // Handle segment type arguments + if (part.contains('<')) { + val argSplit = part.split("<", 2) + if (argSplit.length != 2 || argSplit(1).length < 2 || remaining.last != '>') + Left(s"Unmatched '<' found in '$part'") + else { + buildTypeNames(argSplit(1).take(argSplit(1).length - 1)) match { + case Right(argTypes) => + Right(TypeNameSegment(argSplit.head, argTypes)) + case Left(err) => + Left(err) + } + } + } else { + Right(TypeNameSegment(part)) + } + }) + + val errors = segments.collect { case Left(error) => error } + if (errors.nonEmpty) + return Left(errors.head) + Right(apply(segments.collect { case Right(segment) => segment }.toArray, arraySubscripts)) + } + + /** Split a list of comma delimited type names */ + private def buildTypeNames(value: String): Either[String, Array[UnresolvedTypeRef]] = { + val args = + safeSplit(value, ',').foldLeft(ArrayBuffer[Either[String, UnresolvedTypeRef]]())( + (acc, arg) => { + acc.append(apply(arg)) + acc + } + ) + + args + .collectFirst { case Left(err) => err } + .map(err => Left(err)) + .getOrElse(Right(args.collect { case Right(tn) => tn }.toArray)) + } + + /** Split a string at 'separator' but ignoring if within '<...>' blocks. */ + private def safeSplit(value: String, separator: Char): List[String] = { + var parts: List[String] = Nil + var current = new StringBuffer() + var depth = 0 + value.foreach { + case '<' => depth += 1; current.append('<') + case '>' => depth -= 1; current.append('>') + case x if x == separator && depth == 0 => + parts = current.toString :: parts; current = new StringBuffer() + case x => current.append(x) + } + parts = current.toString :: parts + parts.reverse + } +} + +/** A single segment of a TypeRef, essentially and id with optional type arguments */ +final case class TypeNameSegment(id: IdWithLocation, typeArguments: ArraySeq[TypeRef]) { + + def replaceArguments(args: ArraySeq[TypeRef]): TypeNameSegment = { + TypeNameSegment(id, args) + } + + override def toString: String = { + if (typeArguments.nonEmpty) + s"$id<${typeArguments.map(_.fullName).mkString(",")}>" + else + id.toString + } +} + +object TypeNameSegment { + def apply(name: String): TypeNameSegment = { + new TypeNameSegment(new LocatableId(name, Location.default), TypeRef.emptyArraySeq) + } + + def apply(name: String, typeArguments: ArraySeq[TypeRef]): TypeNameSegment = { + new TypeNameSegment(new LocatableId(name, Location.default), typeArguments) + } + + def apply(name: String, typeArguments: Array[UnresolvedTypeRef]): TypeNameSegment = { + new TypeNameSegment( + new LocatableId(name, Location.default), + ArraySeq.unsafeWrapArray(typeArguments) + ) + } + + def apply(name: String, params: Array[String]): TypeNameSegment = { + new TypeNameSegment(new LocatableId(name, Location.default), TypeRef.toTypeRefs(params)) + } +} diff --git a/shared/src/main/scala/com/financialforce/types/package.scala b/shared/src/main/scala/com/financialforce/types/package.scala new file mode 100644 index 0000000..d813adf --- /dev/null +++ b/shared/src/main/scala/com/financialforce/types/package.scala @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021 FinancialForce.com, inc. All rights reserved. + */ +package com.financialforce + +import scala.collection.immutable.ArraySeq +import scala.collection.mutable + +package object types { + + private[types] implicit class StringOps(value: String) { + /* Improve a formatted string by removing leading/trailing & duplicate whitespaces. */ + def tidyWhitespace: String = value.trim.replaceAll(" +", " ") + } + + /** Simple cache for interning Arrays of values. This needs special handling as Array uses reference equality. */ + private[types] class ArrayInternCache[T] { + private var cache = mutable.HashMap[ArraySeq[T], Array[T]]() + + def intern(value: Array[T]): Array[T] = { + val wrapped = ArraySeq.unsafeWrapArray(value) + cache.getOrElseUpdate(wrapped, value) + } + + def clean(): Unit = { + cache = new mutable.HashMap() + } + } +} From 4cd685bcc77790fde68a2f6cd2a0a0b170e92b87 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 13 Mar 2026 22:57:19 +0000 Subject: [PATCH 2/4] Apply scalafmt formatting to merged apex-types files --- .../financialforce/types/IBodyDeclaration.scala | 5 +++-- .../financialforce/types/IFieldDeclaration.scala | 4 +++- .../financialforce/types/IMethodDeclaration.scala | 4 ++-- .../types/IPropertyDeclaration.scala | 4 +++- .../financialforce/types/ITypeDeclaration.scala | 4 +++- .../scala/com/financialforce/types/IVariable.scala | 4 ++-- .../scala/com/financialforce/types/base/Id.scala | 5 +++-- .../com/financialforce/types/base/Location.scala | 5 +++-- .../com/financialforce/types/base/TypeRef.scala | 14 ++++++++------ .../scala/com/financialforce/types/package.scala | 4 +++- .../com/financialforce/oparser/SmokeTest.scala | 1 - 11 files changed, 33 insertions(+), 21 deletions(-) diff --git a/shared/src/main/scala/com/financialforce/types/IBodyDeclaration.scala b/shared/src/main/scala/com/financialforce/types/IBodyDeclaration.scala index 4099da9..9fba0c0 100644 --- a/shared/src/main/scala/com/financialforce/types/IBodyDeclaration.scala +++ b/shared/src/main/scala/com/financialforce/types/IBodyDeclaration.scala @@ -5,8 +5,9 @@ package com.financialforce.types import com.financialforce.types.base.{IdWithLocation, Location} -/** Common handling for elements that can appear in a class body. bodyLocation define the location of the element - * while blockLocation defines the location of any nested block which will be a subpart of the bodyLocation. +/** Common handling for elements that can appear in a class body. bodyLocation define the location + * of the element while blockLocation defines the location of any nested block which will be a + * subpart of the bodyLocation. */ trait IBodyDeclaration { def id: IdWithLocation diff --git a/shared/src/main/scala/com/financialforce/types/IFieldDeclaration.scala b/shared/src/main/scala/com/financialforce/types/IFieldDeclaration.scala index 7d3f338..aa023c2 100644 --- a/shared/src/main/scala/com/financialforce/types/IFieldDeclaration.scala +++ b/shared/src/main/scala/com/financialforce/types/IFieldDeclaration.scala @@ -5,7 +5,9 @@ package com.financialforce.types import com.financialforce.types.base._ -/** Class field, note custom IVariable equality does not include location information or property blocks. */ +/** Class field, note custom IVariable equality does not include location information or property + * blocks. + */ trait IFieldDeclaration extends IBodyDeclaration with IVariable { override var typeRef: TypeRef override def id: IdWithLocation diff --git a/shared/src/main/scala/com/financialforce/types/IMethodDeclaration.scala b/shared/src/main/scala/com/financialforce/types/IMethodDeclaration.scala index 1ce0b7a..e2292d3 100644 --- a/shared/src/main/scala/com/financialforce/types/IMethodDeclaration.scala +++ b/shared/src/main/scala/com/financialforce/types/IMethodDeclaration.scala @@ -28,8 +28,8 @@ trait IMethodDeclaration extends IBodyDeclaration with AnnotationsAndModifiers { (other.annotations sameElements annotations) && (other.modifiers sameElements modifiers) && ( (other.typeRef.isEmpty && typeRef.isEmpty) || - (other.typeRef.nonEmpty && typeRef.nonEmpty && - other.typeRef.get.sameRef(typeRef.get)) + (other.typeRef.nonEmpty && typeRef.nonEmpty && + other.typeRef.get.sameRef(typeRef.get)) ) && other.id == id && other.formalParameters == formalParameters diff --git a/shared/src/main/scala/com/financialforce/types/IPropertyDeclaration.scala b/shared/src/main/scala/com/financialforce/types/IPropertyDeclaration.scala index eab664b..8aaa68e 100644 --- a/shared/src/main/scala/com/financialforce/types/IPropertyDeclaration.scala +++ b/shared/src/main/scala/com/financialforce/types/IPropertyDeclaration.scala @@ -5,7 +5,9 @@ package com.financialforce.types import com.financialforce.types.base._ -/** Class property, note custom IVariable equality does not include location information or property blocks. */ +/** Class property, note custom IVariable equality does not include location information or property + * blocks. + */ trait IPropertyDeclaration extends IBodyDeclaration with IVariable { def propertyBlocks: Array[PropertyBlock] override def typeRef: TypeRef diff --git a/shared/src/main/scala/com/financialforce/types/ITypeDeclaration.scala b/shared/src/main/scala/com/financialforce/types/ITypeDeclaration.scala index 50de72d..396aa42 100644 --- a/shared/src/main/scala/com/financialforce/types/ITypeDeclaration.scala +++ b/shared/src/main/scala/com/financialforce/types/ITypeDeclaration.scala @@ -7,7 +7,9 @@ import com.financialforce.types.base._ import scala.collection.immutable.ArraySeq -/** Class, enum, interface type declaration. May be used as a resolved TypeRef. Uses Identity equality. */ +/** Class, enum, interface type declaration. May be used as a resolved TypeRef. Uses Identity + * equality. + */ trait ITypeDeclaration extends TypeRef with AnnotationsAndModifiers { def paths: Array[String] def location: Location diff --git a/shared/src/main/scala/com/financialforce/types/IVariable.scala b/shared/src/main/scala/com/financialforce/types/IVariable.scala index 4ab58c4..48cc2b9 100644 --- a/shared/src/main/scala/com/financialforce/types/IVariable.scala +++ b/shared/src/main/scala/com/financialforce/types/IVariable.scala @@ -7,8 +7,8 @@ import com.financialforce.types.base._ import scala.util.hashing.MurmurHash3 -/** Common handling for Variable like elements (fields, properties and formal parameters) that have similar members. - * Note: custom equality does not include location information. +/** Common handling for Variable like elements (fields, properties and formal parameters) that have + * similar members. Note: custom equality does not include location information. */ trait IVariable extends AnnotationsAndModifiers { var typeRef: TypeRef diff --git a/shared/src/main/scala/com/financialforce/types/base/Id.scala b/shared/src/main/scala/com/financialforce/types/base/Id.scala index d6af755..1bd4e0a 100644 --- a/shared/src/main/scala/com/financialforce/types/base/Id.scala +++ b/shared/src/main/scala/com/financialforce/types/base/Id.scala @@ -10,8 +10,9 @@ trait Id { def lowerCaseName: String = name.toLowerCase } -/** Identifier with a location in a file from where it was extracted. Location does not hold path information - * so that needs to be available from some other context, such as the ITypeDeclaration that this is found in. +/** Identifier with a location in a file from where it was extracted. Location does not hold path + * information so that needs to be available from some other context, such as the ITypeDeclaration + * that this is found in. */ trait IdWithLocation extends Id { def location: Location diff --git a/shared/src/main/scala/com/financialforce/types/base/Location.scala b/shared/src/main/scala/com/financialforce/types/base/Location.scala index e2c160d..c48d21b 100644 --- a/shared/src/main/scala/com/financialforce/types/base/Location.scala +++ b/shared/src/main/scala/com/financialforce/types/base/Location.scala @@ -10,8 +10,9 @@ case class Position(line: Int, lineOffset: Int, byteOffset: Int) { } } -/** Location of a range in a source file, logically a start and end position but unrolled to reduce object overhead. - * Note: We don't enforce constraints such as end>=start so consumers should take care, see also Location.default. +/** Location of a range in a source file, logically a start and end position but unrolled to reduce + * object overhead. Note: We don't enforce constraints such as end>=start so consumers should take + * care, see also Location.default. */ case class Location( startLine: Int, diff --git a/shared/src/main/scala/com/financialforce/types/base/TypeRef.scala b/shared/src/main/scala/com/financialforce/types/base/TypeRef.scala index f5c5646..78474c0 100644 --- a/shared/src/main/scala/com/financialforce/types/base/TypeRef.scala +++ b/shared/src/main/scala/com/financialforce/types/base/TypeRef.scala @@ -7,8 +7,9 @@ import scala.collection.immutable.ArraySeq import scala.collection.mutable.ArrayBuffer import scala.util.hashing.MurmurHash3 -/** Reference holder to a type. Typically TypeRefs start as a UnresolvedTypeRef and are replaced by a ITypeDeclaration - * during a resolve phase. The fullName can be used to determine equivalence between these two types of TypeRef. +/** Reference holder to a type. Typically TypeRefs start as a UnresolvedTypeRef and are replaced by + * a ITypeDeclaration during a resolve phase. The fullName can be used to determine equivalence + * between these two types of TypeRef. */ trait TypeRef { def fullName: String @@ -31,8 +32,9 @@ object TypeRef { } } -/** A reference to a type in raw form as it appears in source files. This just captures the segments in the TypeRef - * and any array subscripts. We expect a resolve process to use this information to locate an ITypeDeclaration. +/** A reference to a type in raw form as it appears in source files. This just captures the segments + * in the TypeRef and any array subscripts. We expect a resolve process to use this information to + * locate an ITypeDeclaration. */ final case class UnresolvedTypeRef(typeNameSegments: Array[TypeNameSegment], arraySubscripts: Int) extends TypeRef { @@ -56,8 +58,8 @@ final case class UnresolvedTypeRef(typeNameSegments: Array[TypeNameSegment], arr object UnresolvedTypeRef { - /** Convert a string into a UnresolvedTypeRef. Note: This is really only intended for internal use as the - * error handling is limited. + /** Convert a string into a UnresolvedTypeRef. Note: This is really only intended for internal use + * as the error handling is limited. */ def apply(typeName: String): Either[String, UnresolvedTypeRef] = { diff --git a/shared/src/main/scala/com/financialforce/types/package.scala b/shared/src/main/scala/com/financialforce/types/package.scala index d813adf..0f88d02 100644 --- a/shared/src/main/scala/com/financialforce/types/package.scala +++ b/shared/src/main/scala/com/financialforce/types/package.scala @@ -13,7 +13,9 @@ package object types { def tidyWhitespace: String = value.trim.replaceAll(" +", " ") } - /** Simple cache for interning Arrays of values. This needs special handling as Array uses reference equality. */ + /** Simple cache for interning Arrays of values. This needs special handling as Array uses + * reference equality. + */ private[types] class ArrayInternCache[T] { private var cache = mutable.HashMap[ArraySeq[T], Array[T]]() diff --git a/shared/src/test/scala/com/financialforce/oparser/SmokeTest.scala b/shared/src/test/scala/com/financialforce/oparser/SmokeTest.scala index e1353e1..f4af536 100644 --- a/shared/src/test/scala/com/financialforce/oparser/SmokeTest.scala +++ b/shared/src/test/scala/com/financialforce/oparser/SmokeTest.scala @@ -180,7 +180,6 @@ class SmokeTest extends AnyFunSpec { assert(thrown.getMessage == "Unrecognised method [2.3 -> 2.16] public Dummy ( )") } - it("errors on constructor without body terminated by class end") { val content = """public class Dummy { From 72d15405d9b8ded7637daccf028b41efcc8251e8 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 13 Mar 2026 23:04:16 +0000 Subject: [PATCH 3/4] Update CI workflow to match apex-ls versions - Update Java 8 -> 17 - Update Node 16 -> 22 - Add sbt/setup-sbt@v1 to install sbt - Update checkout action v3 -> v4 This aligns with apex-ls and uses currently supported versions. --- .github/workflows/Build.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index 148c77e..2204d8e 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -17,12 +17,14 @@ jobs: - uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: 8 + java-version: 17 cache: 'sbt' + - uses: sbt/setup-sbt@v1 + - uses: actions/setup-node@v4 with: - node-version: 16 + node-version: 22 - name: FormatCheck run: sbt scalafmtCheck # Does not check test files currently @@ -31,7 +33,7 @@ jobs: run: sbt build - name: Checkout samples repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive repository: apex-dev-tools/apex-samples From fece046f0413da128227e81c805219aa8c60115a Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Fri, 13 Mar 2026 23:11:45 +0000 Subject: [PATCH 4/4] Update CI workflow to match apex-ls versions - Update Java 8 -> 17 - Update Node 16 -> 22 - Update apex-samples v1.0.2 -> v1.4.0 - Add sbt/setup-sbt@v1 to install sbt - Update checkout action v3 -> v4 This aligns with apex-ls and uses currently supported versions. All 6,302 tests pass with these updates. --- .github/workflows/Build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index 2204d8e..97666dd 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -38,7 +38,7 @@ jobs: submodules: recursive repository: apex-dev-tools/apex-samples path: apex-samples - ref: v1.0.2 + ref: v1.4.0 - name: Set samples env run: echo "SAMPLES=$GITHUB_WORKSPACE/apex-samples" >> "$GITHUB_ENV"