Skip to content

Commit

Permalink
Merge pull request #639 from ghostbuster91/fix/i533
Browse files Browse the repository at this point in the history
Add support for using directive in java files
  • Loading branch information
romanowski authored Feb 21, 2022
2 parents 9f9cb73 + eb215fe commit 8752a0b
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 120 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package scala.build.errors

final class DirectiveErrors(errors: ::[String]) extends BuildException(
"Directives errors: " + errors.mkString(", ")
import scala.build.Position

final class DirectiveErrors(errors: ::[String], positions: Seq[Position]) extends BuildException(
"Directives errors: " + errors.mkString(", "),
positions = positions
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package scala.build.preprocessing

import com.virtuslab.using_directives.config.Settings
import com.virtuslab.using_directives.custom.model.{
UsingDirectiveKind,
UsingDirectiveSyntax,
UsingDirectives
}
import com.virtuslab.using_directives.custom.utils.ast.{UsingDef, UsingDefs}
import com.virtuslab.using_directives.{Context, UsingDirectivesProcessor}

import scala.build.errors._
import scala.build.preprocessing.directives.{DirectiveUtil, StrictDirective}
import scala.build.{Logger, Position}
import scala.collection.mutable
import scala.jdk.CollectionConverters._

case class ExtractedDirectives(
offset: Int,
directives: Seq[StrictDirective]
)

object ExtractedDirectives {

val changeToSpecialCommentMsg =
"Using directive using plain comments are deprecated. Please use a special comment syntax: '//> ...' or '/*> ... */'"

def from(
contentChars: Array[Char],
path: Either[String, os.Path],
logger: Logger,
supportedDirectives: Array[UsingDirectiveKind],
cwd: ScopePath
): Either[BuildException, ExtractedDirectives] = {
val errors = new mutable.ListBuffer[Diagnostic]
val reporter = CustomDirectivesReporter.create(path) { diag =>
if (diag.severity == Severity.Warning)
logger.log(Seq(diag))
else
errors += diag
}
val processor = {
val settings = new Settings
settings.setAllowStartWithoutAt(true)
settings.setAllowRequire(false)
val context = new Context(reporter, settings)
new UsingDirectivesProcessor(context)
}
val all = processor.extract(contentChars, true, true).asScala
if (errors.isEmpty) {

def byKind(kind: UsingDirectiveKind) = all.find(_.getKind == kind).get

def getDirectives(directives: UsingDirectives) =
directives.getAst() match {
case ud: UsingDefs =>
ud.getUsingDefs().asScala
case _ =>
Nil
}

val codeDirectives = byKind(UsingDirectiveKind.Code)
val specialCommentDirectives = byKind(UsingDirectiveKind.SpecialComment)
val plainCommentDirectives = byKind(UsingDirectiveKind.PlainComment)

def reportWarning(msg: String, values: Seq[UsingDef], before: Boolean = true): Unit =
values.foreach { v =>
val astPos = v.getPosition()
val (start, end) =
if (before) (0, astPos.getColumn())
else (astPos.getColumn(), astPos.getColumn() + v.getSyntax.getKeyword.size)
val position = Position.File(path, (astPos.getLine(), start), (astPos.getLine(), end))
logger.diagnostic(msg, positions = Seq(position))
}

val usedDirectives =
if (!codeDirectives.getFlattenedMap().isEmpty()) {
val msg =
"This using directive is ignored. File contains directives outside comments and those have higher precedence."
reportWarning(
msg,
getDirectives(plainCommentDirectives) ++ getDirectives(specialCommentDirectives)
)
codeDirectives
}
else if (!specialCommentDirectives.getFlattenedMap().isEmpty()) {
val msg =
s"This using directive is ignored. $changeToSpecialCommentMsg"
reportWarning(msg, getDirectives(plainCommentDirectives))
specialCommentDirectives
}
else {
reportWarning(changeToSpecialCommentMsg, getDirectives(plainCommentDirectives))
plainCommentDirectives
}

// All using directives should use just `using` keyword, no @using or require
reportWarning(
"Deprecated using directive syntax, please use keyword `using`.",
getDirectives(usedDirectives).filter(_.getSyntax() != UsingDirectiveSyntax.Using),
before = false
)

val flattened = usedDirectives.getFlattenedMap.asScala.toSeq
val strictDirectives =
flattened.map {
case (k, l) =>
StrictDirective(k.getPath.asScala.mkString("."), l.asScala.toSeq)
}

val offset =
if (usedDirectives.getKind() != UsingDirectiveKind.Code) 0
else usedDirectives.getCodeOffset()
if (supportedDirectives.contains(usedDirectives.getKind()))
Right(ExtractedDirectives(offset, strictDirectives))
else {
val directiveVales =
usedDirectives.getFlattenedMap.values().asScala.toList.flatMap(_.asScala)
val values = DirectiveUtil.stringValues(directiveVales, path, cwd) ++
DirectiveUtil.numericValues(directiveVales, path, cwd)
Left(new DirectiveErrors(
::(s"Directive '${usedDirectives.getKind}' is not supported in the given context'", Nil),
values.flatMap(_._1.positions)
))
}
}
else {
val errors0 = errors.map(diag => new MalformedDirectiveError(diag.message, diag.positions))
Left(CompositeBuildException(errors0.toSeq))
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package scala.build.preprocessing

import java.nio.charset.StandardCharsets
import com.virtuslab.using_directives.custom.model.UsingDirectiveKind

import java.nio.charset.StandardCharsets
import scala.build.EitherCps.{either, value}
import scala.build.errors.BuildException
import scala.build.options.BuildRequirements
import scala.build.preprocessing.ExtractedDirectives.from
import scala.build.preprocessing.ScalaPreprocessor._
import scala.build.{Inputs, Logger}

case object JavaPreprocessor extends Preprocessor {
Expand All @@ -11,9 +16,32 @@ case object JavaPreprocessor extends Preprocessor {
logger: Logger
): Option[Either[BuildException, Seq[PreprocessedSource]]] =
input match {
case j: Inputs.JavaFile =>
Some(Right(Seq(PreprocessedSource.OnDisk(j.path, None, None, Nil, None))))

case j: Inputs.JavaFile => Some(either {
val content = value(PreprocessingUtil.maybeRead(j.path))
val scopePath = ScopePath.fromPath(j.path)
val ExtractedDirectives(_, directives0) =
value(from(
content.toCharArray,
Right(j.path),
logger,
Array(UsingDirectiveKind.PlainComment, UsingDirectiveKind.SpecialComment),
scopePath
))
val updatedOptions = value(DirectivesProcessor.process(
directives0,
usingDirectiveHandlers,
Right(j.path),
scopePath,
logger
))
Seq(PreprocessedSource.OnDisk(
j.path,
Some(updatedOptions.global),
Some(BuildRequirements()),
Nil,
None
))
})
case v: Inputs.VirtualJavaFile =>
val content = new String(v.content, StandardCharsets.UTF_8)
val s = PreprocessedSource.InMemory(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,17 @@
package scala.build.preprocessing

import com.virtuslab.using_directives.config.Settings
import com.virtuslab.using_directives.custom.model.{
UsingDirectiveKind,
UsingDirectiveSyntax,
UsingDirectives
}
import com.virtuslab.using_directives.custom.utils.ast.{UsingDef, UsingDefs}
import com.virtuslab.using_directives.{Context, UsingDirectivesProcessor}
import com.virtuslab.using_directives.custom.model.UsingDirectiveKind
import dependency.AnyDependency
import dependency.parser.DependencyParser

import java.nio.charset.StandardCharsets

import scala.build.EitherCps.{either, value}
import scala.build.Ops._
import scala.build.errors._
import scala.build.internal.{AmmUtil, Util}
import scala.build.options.{BuildOptions, BuildRequirements, ClassPathOptions, ShadowingSeq}
import scala.build.preprocessing.directives._
import scala.build.{Inputs, Logger, Position, Positioned}
import scala.collection.mutable
import scala.jdk.CollectionConverters._

case object ScalaPreprocessor extends Preprocessor {

Expand Down Expand Up @@ -189,6 +179,7 @@ case object ScalaPreprocessor extends Preprocessor {
): Either[BuildException, Option[SpecialImportsProcessingOutput]] = either {

import fastparse._

import scala.build.internal.ScalaParse._

val res = parse(content, Header(_))
Expand Down Expand Up @@ -274,10 +265,9 @@ case object ScalaPreprocessor extends Preprocessor {
cwd: ScopePath,
logger: Logger
): Either[BuildException, StrictDirectivesProcessingOutput] = either {

val contentChars = content.toCharArray
val ExtractedDirectives(codeOffset, directives0) =
value(extractUsingDirectives(contentChars, path, logger))
value(ExtractedDirectives.from(contentChars, path, logger, UsingDirectiveKind.values(), cwd))

val updatedOptions = value {
DirectivesProcessor.process(
Expand Down Expand Up @@ -347,103 +337,9 @@ case object ScalaPreprocessor extends Preprocessor {
new UnusedDirectiveError(directive.key, values.map(_._1.value), values.flatMap(_._1.positions))
}

case class ExtractedDirectives(offset: Int, directives: Seq[StrictDirective])

val changeToSpecialCommentMsg =
"Using directive using plain comments are deprecated. Please use a special comment syntax: '//> ...' or '/*> ... */'"

def extractUsingDirectives(
contentChars: Array[Char],
path: Either[String, os.Path],
logger: Logger
): Either[BuildException, ExtractedDirectives] = {
val errors = new mutable.ListBuffer[Diagnostic]
val reporter = CustomDirectivesReporter.create(path) { diag =>
if (diag.severity == Severity.Warning)
logger.log(Seq(diag))
else
errors += diag
}
val processor = {
val settings = new Settings
settings.setAllowStartWithoutAt(true)
settings.setAllowRequire(false)
val context = new Context(reporter, settings)
new UsingDirectivesProcessor(context)
}
val all = processor.extract(contentChars, true, true).asScala
if (errors.isEmpty) {

def byKind(kind: UsingDirectiveKind) = all.find(_.getKind == kind).get

def getDirectives(directives: UsingDirectives) =
directives.getAst() match {
case ud: UsingDefs =>
ud.getUsingDefs().asScala.toSeq
case _ =>
Nil
}

val codeDirectives = byKind(UsingDirectiveKind.Code)
val specialCommentDirectives = byKind(UsingDirectiveKind.SpecialComment)
val plainCommentDirectives = byKind(UsingDirectiveKind.PlainComment)

def reportWarning(msg: String, values: Seq[UsingDef], before: Boolean = true): Unit =
values.foreach { v =>
val astPos = v.getPosition()
val (start, end) =
if (before) (0, astPos.getColumn())
else (astPos.getColumn(), astPos.getColumn() + v.getSyntax.getKeyword.size)
val position = Position.File(path, (astPos.getLine(), start), (astPos.getLine(), end))
logger.diagnostic(msg, positions = Seq(position))
}

val usedDirectives =
if (!codeDirectives.getFlattenedMap().isEmpty()) {
val msg =
"This using directive is ignored. File contains directives outside comments and those have higher precedence."
reportWarning(
msg,
getDirectives(plainCommentDirectives) ++ getDirectives(specialCommentDirectives)
)
codeDirectives
}
else if (!specialCommentDirectives.getFlattenedMap().isEmpty()) {
val msg =
s"This using directive is ignored. $changeToSpecialCommentMsg"
reportWarning(msg, getDirectives(plainCommentDirectives))
specialCommentDirectives
}
else {
reportWarning(changeToSpecialCommentMsg, getDirectives(plainCommentDirectives))
plainCommentDirectives
}

// All using directives should use just `using` keyword, no @using or require
reportWarning(
"Deprecated using directive syntax, please use keyword `using`.",
getDirectives(usedDirectives).filter(_.getSyntax() != UsingDirectiveSyntax.Using),
before = false
)

val flattened = usedDirectives.getFlattenedMap.asScala.toSeq
val strictDirectives =
flattened.map {
case (k, l) =>
StrictDirective(k.getPath.asScala.mkString("."), l.asScala.toSeq)
}

val offset =
if (usedDirectives.getKind() != UsingDirectiveKind.Code) 0
else usedDirectives.getCodeOffset()
Right(ExtractedDirectives(offset, strictDirectives))
}
else {
val errors0 = errors.map(diag => new MalformedDirectiveError(diag.message, diag.positions))
Left(CompositeBuildException(errors0.toSeq))
}
}

private def parseDependency(str: String, pos: Position): Either[BuildException, AnyDependency] =
DependencyParser.parse(str) match {
case Left(msg) => Left(new DependencyFormatError(str, msg, positionOpt = Some(pos)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ case object RequireScalaVersionDirectiveHandler extends RequireDirectiveHandler
Right(Some(req))
case _ =>
// TODO: Handle errors and conflicts
Left(new DirectiveErrors(::("Match error in ScalaVersionDirectiveHandler", Nil)))
Left(new DirectiveErrors(::("Match error in ScalaVersionDirectiveHandler", Nil), Seq.empty))
}

def handleValues(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ case object RequireScopeDirectiveHandler extends RequireDirectiveHandler {
scope = Some(BuildRequirements.ScopeRequirement(scope))
)
Right(Some(req))
case _ => Left(new DirectiveErrors(::("No such scope", Nil)))
case _ => Left(new DirectiveErrors(::("No such scope", Nil), Seq.empty))
}

val scoped = values
Expand All @@ -54,7 +54,7 @@ case object RequireScopeDirectiveHandler extends RequireDirectiveHandler {
)
)
Right(req)
case (_, Some(_)) => Left(new DirectiveErrors(::("No such scope", Nil)))
case (_, Some(_)) => Left(new DirectiveErrors(::("No such scope", Nil), Seq.empty))
}
.toSeq
.sequence
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package scala.build
package tests

import com.eed3si9n.expecty.Expecty.{assert => expect}
import com.virtuslab.using_directives.custom.model.UsingDirectiveKind

import scala.build.Logger
import scala.build.errors.Diagnostic
import scala.build.preprocessing.ScalaPreprocessor
import scala.build.preprocessing.{ExtractedDirectives, ScopePath}

class ScalaPreprocessorTests extends munit.FunSuite {

Expand Down Expand Up @@ -40,8 +40,7 @@ class ScalaPreprocessorTests extends munit.FunSuite {
private def testWarnings(lines: String*)(expectedWarnings: Check*): Unit = {
val persistentLogger = new PersistentDiagnosticLogger(Logger.nop)
val code = lines.mkString("\n").toCharArray()
val res =
ScalaPreprocessor.extractUsingDirectives(code, Right(path), persistentLogger)
val res = ExtractedDirectives.from(code, Right(path), persistentLogger, UsingDirectiveKind.values(), ScopePath.fromPath(path))
expect(res.isRight)

val diags = persistentLogger.diagnostics
Expand Down
Loading

0 comments on commit 8752a0b

Please sign in to comment.