From a04328b2eb722b25c2b7472b586c1eaf1a700a56 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Wed, 28 Jan 2026 23:06:12 +0000 Subject: [PATCH] Remove v1 ForceIgnore implementation Simplifies codebase by removing the legacy v1 ForceIgnore implementation and always using ForceIgnoreV2 (node-ignore compatible). Changes: - Delete ForceIgnore.scala (v1 implementation) and ForceIgnoreVersion.scala - Remove ForceIgnoreInterface trait - use ForceIgnoreV2 directly - Update DocumentIndex, Layer, Workspace to always use V2 - Add deprecation warning when forceIgnoreVersion option is set in sfdx-project.json - Remove related test files for v1 implementation - Update tests to verify deprecation warning behavior Closes #383 --- .../pkgforce/documents/DocumentIndex.scala | 22 +- .../nawforce/pkgforce/sfdx/ForceIgnore.scala | 204 ------------------ .../pkgforce/sfdx/ForceIgnoreV2.scala | 2 +- .../pkgforce/sfdx/ForceIgnoreVersion.scala | 42 ---- .../nawforce/pkgforce/sfdx/SFDXProject.scala | 41 ++-- .../nawforce/pkgforce/workspace/Layer.scala | 17 +- .../pkgforce/workspace/Workspace.scala | 12 +- .../documents/DocumentIndexTest.scala | 106 ++------- .../pkgforce/documents/ForceIgnoreTests.scala | 77 ------- .../pkgforce/documents/IgnoreRuleTest.scala | 146 ------------- .../pkgforce/sfdx/ApexConfigTest.scala | 116 +++------- .../sfdx/ForceIgnoreVersionTest.scala | 48 ----- 12 files changed, 83 insertions(+), 750 deletions(-) delete mode 100644 shared/src/main/scala/com/nawforce/pkgforce/sfdx/ForceIgnore.scala delete mode 100644 shared/src/main/scala/com/nawforce/pkgforce/sfdx/ForceIgnoreVersion.scala delete mode 100644 shared/src/test/scala/com/nawforce/pkgforce/documents/ForceIgnoreTests.scala delete mode 100644 shared/src/test/scala/com/nawforce/pkgforce/documents/IgnoreRuleTest.scala delete mode 100644 shared/src/test/scala/com/nawforce/pkgforce/sfdx/ForceIgnoreVersionTest.scala diff --git a/shared/src/main/scala/com/nawforce/pkgforce/documents/DocumentIndex.scala b/shared/src/main/scala/com/nawforce/pkgforce/documents/DocumentIndex.scala index d01e91432..0e9f682c1 100644 --- a/shared/src/main/scala/com/nawforce/pkgforce/documents/DocumentIndex.scala +++ b/shared/src/main/scala/com/nawforce/pkgforce/documents/DocumentIndex.scala @@ -16,12 +16,7 @@ package com.nawforce.pkgforce.documents import com.nawforce.pkgforce.diagnostics._ import com.nawforce.pkgforce.names.{Name, TypeName} import com.nawforce.pkgforce.path.PathLike -import com.nawforce.pkgforce.sfdx.{ - ForceIgnore, - ForceIgnoreInterface, - ForceIgnoreV2, - ForceIgnoreVersion -} +import com.nawforce.pkgforce.sfdx.ForceIgnoreV2 import com.nawforce.runtime.platform.Path import scala.collection.mutable @@ -37,7 +32,7 @@ class DocumentIndex( logger: IssueLogger, namespace: Option[Name], isGulped: Boolean, - ignore: Option[ForceIgnoreInterface] + ignore: Option[ForceIgnoreV2] ) { /** Store Nature->Type name (lowercase)->Path string */ @@ -168,20 +163,15 @@ object DocumentIndex { namespace: Option[Name], isGulped: Boolean, projectPath: PathLike, - path: PathLike, - forceIgnoreVersion: ForceIgnoreVersion = ForceIgnoreVersion.default + path: PathLike ): DocumentIndex = { - val ignore = forceIgnoreVersion match { - case ForceIgnoreVersion.V1 => logger.logAndGet(ForceIgnore(projectPath.join(".forceignore"))) - case ForceIgnoreVersion.V2 => - logger.logAndGet(ForceIgnoreV2(projectPath.join(".forceignore"))) - } + val ignore = logger.logAndGet(ForceIgnoreV2(projectPath.join(".forceignore"))) new DocumentIndex(path, logger, namespace, isGulped, ignore) } private def indexPath( path: PathLike, - forceIgnore: Option[ForceIgnoreInterface], + forceIgnore: Option[ForceIgnoreV2], index: DocumentIndex ): Unit = { @@ -203,7 +193,7 @@ object DocumentIndex { private def addPath( path: PathLike, - forceIgnore: Option[ForceIgnoreInterface], + forceIgnore: Option[ForceIgnoreV2], index: DocumentIndex ): Unit = { // Not testing if this is a regular file to improve scan performance, will fail later on read diff --git a/shared/src/main/scala/com/nawforce/pkgforce/sfdx/ForceIgnore.scala b/shared/src/main/scala/com/nawforce/pkgforce/sfdx/ForceIgnore.scala deleted file mode 100644 index e6f06cc38..000000000 --- a/shared/src/main/scala/com/nawforce/pkgforce/sfdx/ForceIgnore.scala +++ /dev/null @@ -1,204 +0,0 @@ -/* - Copyright (c) 2020 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 com.nawforce.pkgforce.sfdx - -import com.nawforce.pkgforce.diagnostics._ -import com.nawforce.pkgforce.path.{Location, PathLike} -import com.nawforce.runtime.platform.Path - -import java.util.regex.{Matcher, Pattern} -import scala.collection.compat.immutable.ArraySeq -import scala.collection.mutable - -/** Common interface for ForceIgnore implementations */ -trait ForceIgnoreInterface { - def includeDirectory(path: PathLike): Boolean - def includeFile(path: PathLike): Boolean -} - -class ForceIgnore(rootPath: PathLike, ignoreRules: Seq[IgnoreRule]) extends ForceIgnoreInterface { - private val rootPathNative = rootPath.toString - private val rootPathLength = { - rootPathNative.length + (if (rootPathNative.endsWith(Path.separator)) 0 else 1) - } - - def includeDirectory(path: PathLike): Boolean = { - include(path, directory = true) - } - - def includeFile(path: PathLike): Boolean = { - include(path, directory = false) - } - - private def include(path: PathLike, directory: Boolean): Boolean = { - if (!rootPath.isParentOf(path)) - return false - - val relativePath = path.toString.substring(rootPathLength) - var include = true - ignoreRules.foreach(rule => { - if (directory || !rule.dirOnly) { - if (include != rule.negation) { - try { - rule.matcher.reset(relativePath) - if (rule.matcher.matches()) { - include = !include - } - } catch { - case ex: Throwable => - // Catch JVM bug exception and log, see https://github.com/financialforcedev/ff-apex-ls/issues/170 - LoggerOps.info(s"force ignore regex '${rule.regex}' on path '$relativePath'", ex) - } - } - } - }) - include - } -} - -object ForceIgnore { - def apply(path: PathLike): IssuesAnd[Option[ForceIgnore]] = { - if (path.isFile) { - path.read() match { - case Left(err) => - IssuesAnd(ArraySeq(Issue(path, Diagnostic(ERROR_CATEGORY, Location.empty, err))), None) - case Right(data) => - IssuesAnd(ArraySeq(), Some(new ForceIgnore(path.parent, IgnoreRule.read(data)))) - } - } else { - IssuesAnd(None) - } - } -} - -case class IgnoreRule(dirOnly: Boolean, negation: Boolean, pattern: String) { - - lazy val matcher: Matcher = Pattern.compile(regex).matcher("") - - // Convert a pattern to a regex - // See https://github.com/snark/ignorance/blob/master/ignorance/utils.py for reference - lazy val regex: String = { - val builder = new mutable.StringBuilder() - var i = 0 - val n = pattern.length - while (i < n) { - val c = pattern(i) - i = i + 1 - if (c == '*') { - if (i < n && pattern(i) == '*') { - i = i + 1 - builder.append(".*") - if (i < n && pattern(i) == '/') { - i = i + 1 - builder.append(IgnoreRule.escape(Path.separator) + "?") - } - } else { - builder.append(IgnoreRule.nonSep + '*') - } - } else if (c == '?') { - builder.append(IgnoreRule.nonSep) - } else if (c == '/') { - builder.append(IgnoreRule.escape(Path.separator)) - } else if (c == '[') { - var j = i - if (j < n && pattern(j) == '!') - j = j + 1 - if (j < n && pattern(j) == ']') - j = j + 1 - while (j <= n && pattern(j) != ']') { - j = j + 1 - } - if (j >= n) - builder.append("\\[") - else { - var stuff = pattern.substring(i, j).replace("\\", "\\\\") - i = j + 1 - if (stuff(0) == '!') - stuff = s"^ ${stuff.substring(1)}" - else if (stuff(0) == '^') - stuff = s"\\ ${stuff.substring(1)}" - builder.append(s"[$stuff]") - } - } else { - builder.append(IgnoreRule.escapeChar(c)) - } - } - builder.append("$") - builder.toString() - } -} - -object IgnoreRule { - private val nonSep: String = "[^" + escape(Path.separator) + "]" - - /* - * Read rules from ignore file - * See https://github.com/snark/ignorance/blob/master/ignorance/git.py for reference - */ - def read(content: String): Seq[IgnoreRule] = { - content - .split("\n") - .map(_.trim) - .filter(_.nonEmpty) - .filterNot(_.startsWith("#")) - .filterNot(_.contains("***")) - .filter(l => l.split("\\*\\*", -1).length == l.split("/\\*\\*/", -1).length) - .filterNot(_ == "/") - .map(l => { - var pattern = l - var dirOnly = pattern.endsWith("/") - val negation = pattern.startsWith("!") - if (negation) - pattern = pattern.substring(1) - if (pattern.startsWith("/")) - pattern = pattern.substring(1) - if (pattern.startsWith("**")) { - dirOnly = false - pattern = pattern.substring(2) - } - if (pattern.startsWith("/")) - pattern = pattern.substring(1) - if (pattern.endsWith("/")) - pattern = pattern.substring(0, pattern.length - 1) - IgnoreRule(dirOnly, negation, pattern) - }) - .toIndexedSeq - } - - private def escape(s: String): String = { - s.map(escapeChar).mkString - } - - def escapeChar(c: Char): String = { - c match { - case '-' => "\\-" - case '/' => "\\/" - case '\\' => "\\\\" - case '{' => "\\{" - case '}' => "\\}" - case '(' => "\\(" - case ')' => "\\)" - case '*' => "\\*" - case '+' => "\\+" - case '?' => "\\?" - case '.' => "\\." - case ',' => "\\," - case '^' => "\\^" - case '$' => "\\$" - case '|' => "\\|" - case '#' => "\\#" - case _ => c.toString - } - } -} diff --git a/shared/src/main/scala/com/nawforce/pkgforce/sfdx/ForceIgnoreV2.scala b/shared/src/main/scala/com/nawforce/pkgforce/sfdx/ForceIgnoreV2.scala index f635a0774..2428ddae1 100644 --- a/shared/src/main/scala/com/nawforce/pkgforce/sfdx/ForceIgnoreV2.scala +++ b/shared/src/main/scala/com/nawforce/pkgforce/sfdx/ForceIgnoreV2.scala @@ -21,7 +21,7 @@ import scala.collection.compat.immutable.ArraySeq import scala.util.matching.Regex /** ForceIgnoreV2 - node-ignore compatible implementation */ -class ForceIgnoreV2(rootPath: PathLike, rules: Seq[IgnoreRuleV2]) extends ForceIgnoreInterface { +class ForceIgnoreV2(rootPath: PathLike, rules: Seq[IgnoreRuleV2]) { private val allRules = rules diff --git a/shared/src/main/scala/com/nawforce/pkgforce/sfdx/ForceIgnoreVersion.scala b/shared/src/main/scala/com/nawforce/pkgforce/sfdx/ForceIgnoreVersion.scala deleted file mode 100644 index 70aafc1ac..000000000 --- a/shared/src/main/scala/com/nawforce/pkgforce/sfdx/ForceIgnoreVersion.scala +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright (c) 2025 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 com.nawforce.pkgforce.sfdx - -/** Enumeration for ForceIgnore implementation versions */ -sealed abstract class ForceIgnoreVersion(val value: String) - -object ForceIgnoreVersion { - - /** Legacy ForceIgnore implementation */ - case object V1 extends ForceIgnoreVersion("v1") - - /** ForceIgnoreV2 with exact node-ignore 5.3.2 compatibility */ - case object V2 extends ForceIgnoreVersion("v2") - - /** Parse string value to enum */ - def fromString(value: String): Option[ForceIgnoreVersion] = value match { - case "v1" => Some(V1) - case "v2" => Some(V2) - case _ => None - } - - /** Default version */ - val default: ForceIgnoreVersion = V2 - - /** All valid versions */ - val all: Seq[ForceIgnoreVersion] = Seq(V1, V2) - - /** All valid string values */ - val validValues: Seq[String] = all.map(_.value) -} diff --git a/shared/src/main/scala/com/nawforce/pkgforce/sfdx/SFDXProject.scala b/shared/src/main/scala/com/nawforce/pkgforce/sfdx/SFDXProject.scala index 0b3fee050..0dd3624bb 100644 --- a/shared/src/main/scala/com/nawforce/pkgforce/sfdx/SFDXProject.scala +++ b/shared/src/main/scala/com/nawforce/pkgforce/sfdx/SFDXProject.scala @@ -80,27 +80,32 @@ class SFDXProject(val projectPath: PathLike, config: ValueWithPositions) { val apexConfig: ApexConfig = ApexConfig.fromConfig(projectPath, config) - val forceIgnoreVersion: ForceIgnoreVersion = { - val versionString = - apexConfig.options.getOrElse("forceIgnoreVersion", ForceIgnoreVersion.default.value) - ForceIgnoreVersion.fromString(versionString) match { - case Some(version) => version - case None => - val optionsValue = apexConfig.plugins.get("options") - val validValuesStr = ForceIgnoreVersion.validValues.mkString("'", "', '", "'") - config - .lineAndOffsetOf(optionsValue) - .map(lineAndOffset => { - throw SFDXProjectError( - lineAndOffset, - s"'options.forceIgnoreVersion' must be one of $validValuesStr, got '$versionString'" + // Check for deprecated forceIgnoreVersion option and warn if present + apexConfig.options.get("forceIgnoreVersion").foreach { _ => + val optionsValue = apexConfig.plugins.get("options") + config.lineAndOffsetOf(optionsValue) match { + case Some((line, offset)) => + localLogger.log( + Issue( + projectPath.join("sfdx-project.json"), + Diagnostic( + WARNING_CATEGORY, + Location(line, offset), + "'options.forceIgnoreVersion' is deprecated and will be ignored" ) - }) - .getOrElse( - throw new RuntimeException( - s"'options.forceIgnoreVersion' must be one of $validValuesStr, got '$versionString'" + ) + ) + case None => + localLogger.log( + Issue( + projectPath.join("sfdx-project.json"), + Diagnostic( + WARNING_CATEGORY, + Location.empty, + "'options.forceIgnoreVersion' is deprecated and will be ignored" ) ) + ) } } diff --git a/shared/src/main/scala/com/nawforce/pkgforce/workspace/Layer.scala b/shared/src/main/scala/com/nawforce/pkgforce/workspace/Layer.scala index bd626a54d..8bd039468 100644 --- a/shared/src/main/scala/com/nawforce/pkgforce/workspace/Layer.scala +++ b/shared/src/main/scala/com/nawforce/pkgforce/workspace/Layer.scala @@ -17,7 +17,6 @@ import com.nawforce.pkgforce.diagnostics.IssueLogger import com.nawforce.pkgforce.documents.DocumentIndex import com.nawforce.pkgforce.names.Name import com.nawforce.pkgforce.path.PathLike -import com.nawforce.pkgforce.sfdx.ForceIgnoreVersion /** Project metadata is modeled as an ordered sequence of namespace layers which contain an ordered * sequence of module layers. The layers should be ordered by deploy order, this means external @@ -34,12 +33,9 @@ sealed trait Layer */ case class NamespaceLayer(namespace: Option[Name], isGulped: Boolean, layers: Seq[ModuleLayer]) extends Layer { - def indexes( - logger: IssueLogger, - forceIgnoreVersion: ForceIgnoreVersion = ForceIgnoreVersion.default - ): Map[ModuleLayer, DocumentIndex] = + def indexes(logger: IssueLogger): Map[ModuleLayer, DocumentIndex] = layers.foldLeft(Map[ModuleLayer, DocumentIndex]())((acc, layer) => - acc + (layer -> layer.index(logger, namespace, isGulped, forceIgnoreVersion)) + acc + (layer -> layer.index(logger, namespace, isGulped)) ) } @@ -59,12 +55,7 @@ case class ModuleLayer( path.toString.substring(root.toString.length) } - def index( - logger: IssueLogger, - namespace: Option[Name], - isGulped: Boolean, - forceIgnoreVersion: ForceIgnoreVersion = ForceIgnoreVersion.default - ): DocumentIndex = { - DocumentIndex(logger, namespace, isGulped, projectPath, path, forceIgnoreVersion) + def index(logger: IssueLogger, namespace: Option[Name], isGulped: Boolean): DocumentIndex = { + DocumentIndex(logger, namespace, isGulped, projectPath, path) } } diff --git a/shared/src/main/scala/com/nawforce/pkgforce/workspace/Workspace.scala b/shared/src/main/scala/com/nawforce/pkgforce/workspace/Workspace.scala index 817f1067a..5d1856a1f 100644 --- a/shared/src/main/scala/com/nawforce/pkgforce/workspace/Workspace.scala +++ b/shared/src/main/scala/com/nawforce/pkgforce/workspace/Workspace.scala @@ -17,7 +17,7 @@ import com.nawforce.pkgforce.diagnostics.IssueLogger import com.nawforce.pkgforce.documents.{DocumentIndex, MetadataDocument} import com.nawforce.pkgforce.names.TypeName import com.nawforce.pkgforce.path.{Location, PathLike} -import com.nawforce.pkgforce.sfdx.{ForceIgnoreVersion, SFDXProject} +import com.nawforce.pkgforce.sfdx.SFDXProject import com.nawforce.pkgforce.stream.{PackageEvent, PackageStream} /** Contains any config option that can be used by the Org @@ -38,15 +38,12 @@ case class Workspace( logger: IssueLogger, layers: Seq[NamespaceLayer], projectConfig: Option[ProjectConfig] = None, - externalMetadataPaths: Seq[PathLike] = Seq.empty, - forceIgnoreVersion: ForceIgnoreVersion = ForceIgnoreVersion.default + externalMetadataPaths: Seq[PathLike] = Seq.empty ) { // Document indexes for each layer of actual metadata val indexes: Map[ModuleLayer, DocumentIndex] = - layers.foldLeft(Map[ModuleLayer, DocumentIndex]())((acc, layer) => - acc ++ layer.indexes(logger, forceIgnoreVersion) - ) + layers.foldLeft(Map[ModuleLayer, DocumentIndex]())((acc, layer) => acc ++ layer.indexes(logger)) def get(typeName: TypeName): List[MetadataDocument] = { val indexes = deployOrderedIndexes.toSeq.reverse @@ -79,8 +76,7 @@ object Workspace { issueManager, layers, Some(ProjectConfig(proj.maxDependencyCount, proj.isLibrary)), - proj.externalMetadataPaths, - proj.forceIgnoreVersion + proj.externalMetadataPaths ) } } diff --git a/shared/src/test/scala/com/nawforce/pkgforce/documents/DocumentIndexTest.scala b/shared/src/test/scala/com/nawforce/pkgforce/documents/DocumentIndexTest.scala index 1a0577abb..efd096231 100644 --- a/shared/src/test/scala/com/nawforce/pkgforce/documents/DocumentIndexTest.scala +++ b/shared/src/test/scala/com/nawforce/pkgforce/documents/DocumentIndexTest.scala @@ -16,7 +16,6 @@ package com.nawforce.pkgforce.documents import com.nawforce.pkgforce.diagnostics.IssueLogger import com.nawforce.pkgforce.names.Name import com.nawforce.pkgforce.path.PathLike -import com.nawforce.pkgforce.sfdx.ForceIgnoreVersion import com.nawforce.runtime.FileSystemHelper import org.scalatest.BeforeAndAfter import org.scalatest.funsuite.AnyFunSuite @@ -31,14 +30,7 @@ class DocumentIndexTest extends AnyFunSuite with BeforeAndAfter { test("bad dir has no files") { FileSystemHelper.run(Map[String, String]()) { root: PathLike => - val index = DocumentIndex( - logger, - None, - isGulped = false, - root, - root.join("foo"), - ForceIgnoreVersion.default - ) + val index = DocumentIndex(logger, None, isGulped = false, root, root.join("foo")) assert(logger.isEmpty) assert(index.size == 0) } @@ -47,7 +39,7 @@ class DocumentIndexTest extends AnyFunSuite with BeforeAndAfter { test("empty dir has no files") { FileSystemHelper.run(Map[String, String]()) { root: PathLike => val index = - DocumentIndex(logger, None, isGulped = false, root, root, ForceIgnoreVersion.default) + DocumentIndex(logger, None, isGulped = false, root, root) assert(logger.isEmpty) assert(index.size == 0) } @@ -56,7 +48,7 @@ class DocumentIndexTest extends AnyFunSuite with BeforeAndAfter { test("dot dir is ignored") { FileSystemHelper.run(Map[String, String](".pkg/Foo.cls" -> "")) { root: PathLike => val index = - DocumentIndex(logger, None, isGulped = false, root, root, ForceIgnoreVersion.default) + DocumentIndex(logger, None, isGulped = false, root, root) assert(logger.isEmpty) assert(index.size == 0) } @@ -65,7 +57,7 @@ class DocumentIndexTest extends AnyFunSuite with BeforeAndAfter { test("node_modules dir is ignored") { FileSystemHelper.run(Map[String, String]("node_modules/Foo.cls" -> "")) { root: PathLike => val index = - DocumentIndex(logger, None, isGulped = false, root, root, ForceIgnoreVersion.default) + DocumentIndex(logger, None, isGulped = false, root, root) assert(logger.isEmpty) assert(index.size == 0) } @@ -74,14 +66,7 @@ class DocumentIndexTest extends AnyFunSuite with BeforeAndAfter { test("class file found") { FileSystemHelper.run(Map[String, String]("pkg/Foo.cls" -> "public class Foo {}")) { root: PathLike => - val index = DocumentIndex( - logger, - None, - isGulped = false, - root, - root.join("pkg"), - ForceIgnoreVersion.default - ) + val index = DocumentIndex(logger, None, isGulped = false, root, root.join("pkg")) assert(logger.isEmpty) assert(index.get(ApexNature).size == 1) assert( @@ -94,14 +79,7 @@ class DocumentIndexTest extends AnyFunSuite with BeforeAndAfter { test("nested class file found") { FileSystemHelper.run(Map[String, String]("pkg/foo/Foo.cls" -> "public class Foo {}")) { root: PathLike => - val index = DocumentIndex( - logger, - None, - isGulped = false, - root, - root.join("pkg"), - ForceIgnoreVersion.default - ) + val index = DocumentIndex(logger, None, isGulped = false, root, root.join("pkg")) assert(logger.isEmpty) assert(index.get(ApexNature).size == 1) assert( @@ -118,14 +96,7 @@ class DocumentIndexTest extends AnyFunSuite with BeforeAndAfter { "/pkg/bar/Bar.cls" -> "public class Bar {}" ) ) { root: PathLike => - val index = DocumentIndex( - logger, - None, - isGulped = false, - root, - root.join("pkg"), - ForceIgnoreVersion.default - ) + val index = DocumentIndex(logger, None, isGulped = false, root, root.join("pkg")) assert(logger.isEmpty) assert( index.getControllingDocuments(ApexNature).map(_.toString()).toSet == Set( @@ -143,14 +114,7 @@ class DocumentIndexTest extends AnyFunSuite with BeforeAndAfter { "pkg/bar/Foo.cls" -> "public class Foo {}" ) ) { root: PathLike => - val index = DocumentIndex( - logger, - None, - isGulped = false, - root, - root.join("pkg"), - ForceIgnoreVersion.default - ) + val index = DocumentIndex(logger, None, isGulped = false, root, root.join("pkg")) assert(index.get(ApexNature).size == 1) val issues = logger.issuesForFiles(null, includeWarnings = false, 10) assert(issues.length == 1) @@ -165,14 +129,7 @@ class DocumentIndexTest extends AnyFunSuite with BeforeAndAfter { "/pkg/bar/CustomLabels.labels" -> "" ) ) { root: PathLike => - val index = DocumentIndex( - logger, - None, - isGulped = false, - root, - root.join("pkg"), - ForceIgnoreVersion.default - ) + val index = DocumentIndex(logger, None, isGulped = false, root, root.join("pkg")) assert(logger.isEmpty) assert(index.getControllingDocuments(LabelNature).size == 2) } @@ -184,14 +141,7 @@ class DocumentIndexTest extends AnyFunSuite with BeforeAndAfter { "pkg/objects/Foo.object" -> "" ) ) { root: PathLike => - val index = DocumentIndex( - logger, - None, - isGulped = false, - root, - root.join("pkg"), - ForceIgnoreVersion.default - ) + val index = DocumentIndex(logger, None, isGulped = false, root, root.join("pkg")) assert(logger.isEmpty) assert( index.getControllingDocuments(SObjectNature) == @@ -206,14 +156,7 @@ class DocumentIndexTest extends AnyFunSuite with BeforeAndAfter { "pkg/objects/Bar/Foo.object-meta.xml" -> "" ) ) { root: PathLike => - val index = DocumentIndex( - logger, - None, - isGulped = false, - root, - root.join("pkg"), - ForceIgnoreVersion.default - ) + val index = DocumentIndex(logger, None, isGulped = false, root, root.join("pkg")) assert(logger.isEmpty) assert( index.getControllingDocuments(SObjectNature) == @@ -230,14 +173,7 @@ class DocumentIndexTest extends AnyFunSuite with BeforeAndAfter { "pkg/objects/Foo__c.object" -> "" ) ) { root: PathLike => - val index = DocumentIndex( - logger, - None, - isGulped = false, - root, - root.join("pkg"), - ForceIgnoreVersion.default - ) + val index = DocumentIndex(logger, None, isGulped = false, root, root.join("pkg")) assert(logger.isEmpty) assert( index.getControllingDocuments(SObjectNature) == @@ -252,14 +188,7 @@ class DocumentIndexTest extends AnyFunSuite with BeforeAndAfter { "pkg/objects/Bar__c/Foo__c.object-meta.xml" -> "" ) ) { root: PathLike => - val index = DocumentIndex( - logger, - None, - isGulped = false, - root, - root.join("pkg"), - ForceIgnoreVersion.default - ) + val index = DocumentIndex(logger, None, isGulped = false, root, root.join("pkg")) assert(logger.isEmpty) assert( index.getControllingDocuments(SObjectNature) == @@ -279,14 +208,7 @@ class DocumentIndexTest extends AnyFunSuite with BeforeAndAfter { "pkg/Bar/Foo.object" -> "" ) ) { root: PathLike => - val index = DocumentIndex( - logger, - None, - isGulped = false, - root, - root.join("pkg"), - ForceIgnoreVersion.default - ) + val index = DocumentIndex(logger, None, isGulped = false, root, root.join("pkg")) assert(logger.isEmpty) assert(index.getControllingDocuments(SObjectNature).isEmpty) } diff --git a/shared/src/test/scala/com/nawforce/pkgforce/documents/ForceIgnoreTests.scala b/shared/src/test/scala/com/nawforce/pkgforce/documents/ForceIgnoreTests.scala deleted file mode 100644 index 943245758..000000000 --- a/shared/src/test/scala/com/nawforce/pkgforce/documents/ForceIgnoreTests.scala +++ /dev/null @@ -1,77 +0,0 @@ -/* - Copyright (c) 2020 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 com.nawforce.pkgforce.documents - -import com.nawforce.pkgforce.path.PathLike -import com.nawforce.pkgforce.sfdx.ForceIgnore -import com.nawforce.runtime.FileSystemHelper -import org.scalatest.funsuite.AnyFunSuite - -class ForceIgnoreTests extends AnyFunSuite { - - test("Empty ignore") { - FileSystemHelper.runTempDir(Map[String, String](".forceignore" -> "")) { root: PathLike => - val ignore = - ForceIgnore(root.join(".forceignore")).value.getOrElse(throw new NoSuchElementException()) - assert(ignore.includeDirectory(root.join("foo"))) - assert(ignore.includeFile(root.join("foo"))) - } - } - - test("Simple ignore") { - FileSystemHelper.runTempDir(Map[String, String](".forceignore" -> "foo")) { root: PathLike => - val ignore = - ForceIgnore(root.join(".forceignore")).value.getOrElse(throw new NoSuchElementException()) - assert(!ignore.includeDirectory(root.join("foo"))) - assert(!ignore.includeFile(root.join("foo"))) - assert(ignore.includeDirectory(root.join("foo2"))) - assert(ignore.includeFile(root.join("2foo"))) - } - } - - test("Directory ignore") { - FileSystemHelper.runTempDir(Map[String, String](".forceignore" -> "foo/")) { root: PathLike => - val ignore = - ForceIgnore(root.join(".forceignore")).value.getOrElse(throw new NoSuchElementException()) - assert(!ignore.includeDirectory(root.join("foo"))) - assert(ignore.includeFile(root.join("foo"))) - assert(ignore.includeDirectory(root.join("foo2"))) - assert(ignore.includeFile(root.join("2foo"))) - } - } - - test("Simple negate") { - FileSystemHelper.runTempDir(Map[String, String](".forceignore" -> "f*\n!foo")) { - root: PathLike => - val ignore = - ForceIgnore(root.join(".forceignore")).value.getOrElse(throw new NoSuchElementException()) - assert(ignore.includeDirectory(root.join("foo"))) - assert(ignore.includeFile(root.join("foo"))) - assert(!ignore.includeDirectory(root.join("foo2"))) - assert(ignore.includeFile(root.join("2foo"))) - } - } - - test("Directory negate") { - FileSystemHelper.runTempDir(Map[String, String](".forceignore" -> "f*\n!foo/")) { - root: PathLike => - val ignore = - ForceIgnore(root.join(".forceignore")).value.getOrElse(throw new NoSuchElementException()) - assert(ignore.includeDirectory(root.join("foo"))) - assert(!ignore.includeFile(root.join("foo"))) - assert(!ignore.includeDirectory(root.join("foo2"))) - assert(ignore.includeFile(root.join("2foo"))) - } - } -} diff --git a/shared/src/test/scala/com/nawforce/pkgforce/documents/IgnoreRuleTest.scala b/shared/src/test/scala/com/nawforce/pkgforce/documents/IgnoreRuleTest.scala deleted file mode 100644 index 6703e8b4b..000000000 --- a/shared/src/test/scala/com/nawforce/pkgforce/documents/IgnoreRuleTest.scala +++ /dev/null @@ -1,146 +0,0 @@ -/* - Copyright (c) 2020 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 com.nawforce.pkgforce.documents - -import com.nawforce.pkgforce.sfdx.IgnoreRule -import org.scalatest.funsuite.AnyFunSuite - -class IgnoreRuleTest extends AnyFunSuite { - - test("Character escapes") { - assert(IgnoreRule.escapeChar('/') == "\\/") - assert(IgnoreRule.escapeChar('\\') == "\\\\") - assert(IgnoreRule.escapeChar('+') == "\\+") - assert(IgnoreRule.escapeChar('#') == "\\#") - assert(IgnoreRule.escapeChar('a') == "a") - assert(IgnoreRule.escapeChar('0') == "0") - } - - test("Empty rules") { - assert(IgnoreRule.read("").isEmpty) - } - - test("Whitespace rule") { - assert(IgnoreRule.read(" ").isEmpty) - } - - test("Comment rule") { - assert(IgnoreRule.read("# A comment").isEmpty) - } - - test("Two many *") { - assert(IgnoreRule.read("a/***/b").isEmpty) - } - - test("** at front") { - assert(IgnoreRule.read("**/b").isEmpty) - } - - test("** at back") { - assert(IgnoreRule.read("a/**").isEmpty) - } - - test("Legal use of **") { - assert(IgnoreRule.read("a/**/b").size == 1) - } - - test("Just a /") { - assert(IgnoreRule.read("/").isEmpty) - } - - test("A name") { - assert(IgnoreRule.read("foo") == Seq(IgnoreRule(dirOnly = false, negation = false, "foo"))) - } - - test("An anchored name") { - assert(IgnoreRule.read("foo/") == Seq(IgnoreRule(dirOnly = true, negation = false, "foo"))) - } - - test("An negated name") { - assert(IgnoreRule.read("!foo") == Seq(IgnoreRule(dirOnly = false, negation = true, "foo"))) - } - - test("An anchored & negated name") { - assert(IgnoreRule.read("!foo/") == Seq(IgnoreRule(dirOnly = true, negation = true, "foo"))) - } - - test("A sub-path") { - assert( - IgnoreRule.read("foo/bar") == Seq(IgnoreRule(dirOnly = false, negation = false, "foo/bar")) - ) - } - - test("A sub-path with wildcard") { - assert( - IgnoreRule - .read("foo/*/bar") == Seq(IgnoreRule(dirOnly = false, negation = false, "foo/*/bar")) - ) - } - - test("A sub-path with double wildcard") { - assert( - IgnoreRule - .read("foo/**/bar") == Seq(IgnoreRule(dirOnly = false, negation = false, "foo/**/bar")) - ) - } - - test("Leading /") { - assert(IgnoreRule.read("/foo") == Seq(IgnoreRule(dirOnly = false, negation = false, "foo"))) - } - - test("Leading /**/") { - assert(IgnoreRule.read("/**/foo") == Seq(IgnoreRule(dirOnly = false, negation = false, "foo"))) - } - - test("A name regex") { - assert(IgnoreRule.read("foo").head.regex == "foo$") - } - - test("A name with question mark regex") { - val re = IgnoreRule.read("f?o").head.regex - assert(re == "f[^\\/]o$" || re == "f[^\\\\]o$") - } - - test("A name with wildcard regex") { - val re = IgnoreRule.read("f*o").head.regex - assert(re == "f[^\\/]*o$" || re == "f[^\\\\]*o$") - } - - test("A name with range regex") { - assert(IgnoreRule.read("f[a-Z]o").head.regex == "f[a-Z]o$") - } - - test("A sub-path regex") { - val re = IgnoreRule.read("foo/bar").head.regex - assert(re == "foo\\/bar$" || re == "foo\\\\bar$") - } - - test("A sub-path with wildcard regex") { - val re = IgnoreRule.read("foo/*/bar").head.regex - assert(re == "foo\\/[^\\/]*\\/bar$" || re == "foo\\\\[^\\\\]*\\\\bar$") - } - - test("A sub-path with double wildcard regex") { - val re = IgnoreRule.read("foo/**/bar").head.regex - assert(re == "foo\\/.*\\/?bar$" || re == "foo\\\\.*\\\\?bar$") - } - - test("Leading / regex") { - assert(IgnoreRule.read("/foo").head.regex == "foo$") - } - - test("Leading /**/ regex") { - assert(IgnoreRule.read("/**/foo").head.regex == "foo$") - } -} diff --git a/shared/src/test/scala/com/nawforce/pkgforce/sfdx/ApexConfigTest.scala b/shared/src/test/scala/com/nawforce/pkgforce/sfdx/ApexConfigTest.scala index b436b3eb9..f2b04dc6c 100644 --- a/shared/src/test/scala/com/nawforce/pkgforce/sfdx/ApexConfigTest.scala +++ b/shared/src/test/scala/com/nawforce/pkgforce/sfdx/ApexConfigTest.scala @@ -75,13 +75,13 @@ class ApexConfigTest extends AnyFunSuite with BeforeAndAfter { test("Legacy configuration - with options") { FileSystemHelper.run( Map( - "sfdx-project.json" -> "{\"plugins\": {\"options\": {\"forceIgnoreVersion\": \"v1\"}}, \"packageDirectories\": []}" + "sfdx-project.json" -> "{\"plugins\": {\"options\": {\"customOption\": \"value\"}}, \"packageDirectories\": []}" ) ) { root: PathLike => val project = SFDXProject(root, logger) assert(logger.issues.isEmpty) assert(project.nonEmpty) - assert(project.get.apexConfig.options == Map("forceIgnoreVersion" -> "v1")) + assert(project.get.apexConfig.options == Map("customOption" -> "value")) } } @@ -152,9 +152,9 @@ class ApexConfigTest extends AnyFunSuite with BeforeAndAfter { "sfdx-project.json" -> """{ | "plugins": { - | "options": {"forceIgnoreVersion": "v1", "legacyOption": "legacy"}, + | "options": {"legacyOption": "legacy"}, | "apex-ls": { - | "options": {"forceIgnoreVersion": "v2", "newOption": "new"} + | "options": {"newOption": "new"} | } | }, | "packageDirectories": [] @@ -165,9 +165,7 @@ class ApexConfigTest extends AnyFunSuite with BeforeAndAfter { assert(logger.issues.isEmpty) assert(project.nonEmpty) // apex-ls options completely replace legacy options (not merged) - assert( - project.get.apexConfig.options == Map("forceIgnoreVersion" -> "v2", "newOption" -> "new") - ) + assert(project.get.apexConfig.options == Map("newOption" -> "new")) } } @@ -357,66 +355,63 @@ class ApexConfigTest extends AnyFunSuite with BeforeAndAfter { } } - // ForceIgnoreVersion integration tests - verify end-to-end functionality works - test("ForceIgnoreVersion - default behavior") { + // forceIgnoreVersion deprecation tests - option is now deprecated and ignored + test("forceIgnoreVersion - no warning when not present") { FileSystemHelper.run(Map("sfdx-project.json" -> "{\"packageDirectories\": []}")) { root: PathLike => val project = SFDXProject(root, logger) assert(logger.issues.isEmpty) assert(project.nonEmpty) assert(project.get.apexConfig.options.isEmpty) - assert(project.get.forceIgnoreVersion == ForceIgnoreVersion.V2) // Default value } } - test("ForceIgnoreVersion - empty options") { - FileSystemHelper.run( - Map("sfdx-project.json" -> "{\"plugins\": {\"options\": {}}, \"packageDirectories\": []}") - ) { root: PathLike => - val project = SFDXProject(root, logger) - assert(logger.issues.isEmpty) - assert(project.nonEmpty) - assert(project.get.apexConfig.options.isEmpty) - assert(project.get.forceIgnoreVersion == ForceIgnoreVersion.V2) // Default value - } - } - - test("ForceIgnoreVersion - v1 setting works end-to-end") { + test("forceIgnoreVersion - deprecation warning when set to v1") { FileSystemHelper.run( Map( "sfdx-project.json" -> "{\"plugins\": {\"options\": {\"forceIgnoreVersion\": \"v1\"}}, \"packageDirectories\": []}" ) ) { root: PathLike => val project = SFDXProject(root, logger) - assert(logger.issues.isEmpty) assert(project.nonEmpty) assert(project.get.apexConfig.options == Map("forceIgnoreVersion" -> "v1")) - assert(project.get.forceIgnoreVersion == ForceIgnoreVersion.V1) // End-to-end functionality + // Warning is emitted when layers() is called + project.get.layers(logger) + assert( + logger.issues.exists(i => + i.diagnostic.category == WARNING_CATEGORY && + i.diagnostic.message.contains("'options.forceIgnoreVersion' is deprecated") + ) + ) } } - test("ForceIgnoreVersion - v2 setting works end-to-end") { + test("forceIgnoreVersion - deprecation warning when set to v2") { FileSystemHelper.run( Map( "sfdx-project.json" -> "{\"plugins\": {\"options\": {\"forceIgnoreVersion\": \"v2\"}}, \"packageDirectories\": []}" ) ) { root: PathLike => val project = SFDXProject(root, logger) - assert(logger.issues.isEmpty) assert(project.nonEmpty) - assert(project.get.apexConfig.options == Map("forceIgnoreVersion" -> "v2")) - assert(project.get.forceIgnoreVersion == ForceIgnoreVersion.V2) // End-to-end functionality + // Warning is emitted when layers() is called + project.get.layers(logger) + assert( + logger.issues.exists(i => + i.diagnostic.category == WARNING_CATEGORY && + i.diagnostic.message.contains("'options.forceIgnoreVersion' is deprecated") + ) + ) } } - test("ForceIgnoreVersion - with multiple options") { + test("forceIgnoreVersion - deprecation warning with other options") { FileSystemHelper.run( Map( "sfdx-project.json" -> "{\"plugins\": {\"options\": {\"forceIgnoreVersion\": \"v1\", \"customOption\": \"value\"}}, \"packageDirectories\": []}" ) ) { root: PathLike => val project = SFDXProject(root, logger) - assert(logger.issues.isEmpty) assert(project.nonEmpty) assert( project.get.apexConfig.options == Map( @@ -424,63 +419,14 @@ class ApexConfigTest extends AnyFunSuite with BeforeAndAfter { "customOption" -> "value" ) ) - assert(project.get.forceIgnoreVersion == ForceIgnoreVersion.V1) // End-to-end functionality - } - } - - test("ForceIgnoreVersion - invalid value rejected") { - FileSystemHelper.run( - Map( - "sfdx-project.json" -> "{\"plugins\": {\"options\": {\"forceIgnoreVersion\": \"v3\"}}, \"packageDirectories\": []}" - ) - ) { root: PathLike => - val project = SFDXProject(root, logger) - assert(project.isEmpty) // Should fail due to invalid version + // Warning is emitted when layers() is called + project.get.layers(logger) assert( - logger.issues.exists( - _.diagnostic.message - .contains("'options.forceIgnoreVersion' must be one of 'v1', 'v2', got 'v3'") + logger.issues.exists(i => + i.diagnostic.category == WARNING_CATEGORY && + i.diagnostic.message.contains("'options.forceIgnoreVersion' is deprecated") ) ) } } - - test("ForceIgnoreVersion - non-string value rejected") { - FileSystemHelper.run( - Map( - "sfdx-project.json" -> "{\"plugins\": {\"options\": {\"forceIgnoreVersion\": 123}}, \"packageDirectories\": []}" - ) - ) { root: PathLike => - val project = SFDXProject(root, logger) - assert(project.isEmpty) // Should fail due to non-string value - assert( - logger.issues.exists( - _.diagnostic.message.contains("'options.forceIgnoreVersion' should be a string value") - ) - ) - } - } - - test("ForceIgnoreVersion - apex-ls precedence") { - FileSystemHelper.run( - Map( - "sfdx-project.json" -> - """{ - | "plugins": { - | "options": {"forceIgnoreVersion": "v1"}, - | "apex-ls": { - | "options": {"forceIgnoreVersion": "v2"} - | } - | }, - | "packageDirectories": [] - |}""".stripMargin - ) - ) { root: PathLike => - val project = SFDXProject(root, logger) - assert(logger.issues.isEmpty) - assert(project.nonEmpty) - assert(project.get.apexConfig.options == Map("forceIgnoreVersion" -> "v2")) - assert(project.get.forceIgnoreVersion == ForceIgnoreVersion.V2) // apex-ls wins - } - } } diff --git a/shared/src/test/scala/com/nawforce/pkgforce/sfdx/ForceIgnoreVersionTest.scala b/shared/src/test/scala/com/nawforce/pkgforce/sfdx/ForceIgnoreVersionTest.scala deleted file mode 100644 index 4f1644c37..000000000 --- a/shared/src/test/scala/com/nawforce/pkgforce/sfdx/ForceIgnoreVersionTest.scala +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright (c) 2025 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 com.nawforce.pkgforce.sfdx - -import org.scalatest.funsuite.AnyFunSuite - -class ForceIgnoreVersionTest extends AnyFunSuite { - - test("fromString valid values") { - assert(ForceIgnoreVersion.fromString("v1") == Some(ForceIgnoreVersion.V1)) - assert(ForceIgnoreVersion.fromString("v2") == Some(ForceIgnoreVersion.V2)) - } - - test("fromString invalid values") { - assert(ForceIgnoreVersion.fromString("v3") == None) - assert(ForceIgnoreVersion.fromString("invalid") == None) - assert(ForceIgnoreVersion.fromString("") == None) - assert(ForceIgnoreVersion.fromString("V1") == None) // Case sensitive - } - - test("value property") { - assert(ForceIgnoreVersion.V1.value == "v1") - assert(ForceIgnoreVersion.V2.value == "v2") - } - - test("default value") { - assert(ForceIgnoreVersion.default == ForceIgnoreVersion.V2) - } - - test("all versions") { - assert(ForceIgnoreVersion.all == Seq(ForceIgnoreVersion.V1, ForceIgnoreVersion.V2)) - } - - test("valid values") { - assert(ForceIgnoreVersion.validValues == Seq("v1", "v2")) - } -}