Skip to content

Commit 8752a0b

Browse files
authored
Merge pull request #639 from ghostbuster91/fix/i533
Add support for using directive in java files
2 parents 9f9cb73 + eb215fe commit 8752a0b

File tree

8 files changed

+235
-120
lines changed

8 files changed

+235
-120
lines changed
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package scala.build.errors
22

3-
final class DirectiveErrors(errors: ::[String]) extends BuildException(
4-
"Directives errors: " + errors.mkString(", ")
3+
import scala.build.Position
4+
5+
final class DirectiveErrors(errors: ::[String], positions: Seq[Position]) extends BuildException(
6+
"Directives errors: " + errors.mkString(", "),
7+
positions = positions
58
)
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package scala.build.preprocessing
2+
3+
import com.virtuslab.using_directives.config.Settings
4+
import com.virtuslab.using_directives.custom.model.{
5+
UsingDirectiveKind,
6+
UsingDirectiveSyntax,
7+
UsingDirectives
8+
}
9+
import com.virtuslab.using_directives.custom.utils.ast.{UsingDef, UsingDefs}
10+
import com.virtuslab.using_directives.{Context, UsingDirectivesProcessor}
11+
12+
import scala.build.errors._
13+
import scala.build.preprocessing.directives.{DirectiveUtil, StrictDirective}
14+
import scala.build.{Logger, Position}
15+
import scala.collection.mutable
16+
import scala.jdk.CollectionConverters._
17+
18+
case class ExtractedDirectives(
19+
offset: Int,
20+
directives: Seq[StrictDirective]
21+
)
22+
23+
object ExtractedDirectives {
24+
25+
val changeToSpecialCommentMsg =
26+
"Using directive using plain comments are deprecated. Please use a special comment syntax: '//> ...' or '/*> ... */'"
27+
28+
def from(
29+
contentChars: Array[Char],
30+
path: Either[String, os.Path],
31+
logger: Logger,
32+
supportedDirectives: Array[UsingDirectiveKind],
33+
cwd: ScopePath
34+
): Either[BuildException, ExtractedDirectives] = {
35+
val errors = new mutable.ListBuffer[Diagnostic]
36+
val reporter = CustomDirectivesReporter.create(path) { diag =>
37+
if (diag.severity == Severity.Warning)
38+
logger.log(Seq(diag))
39+
else
40+
errors += diag
41+
}
42+
val processor = {
43+
val settings = new Settings
44+
settings.setAllowStartWithoutAt(true)
45+
settings.setAllowRequire(false)
46+
val context = new Context(reporter, settings)
47+
new UsingDirectivesProcessor(context)
48+
}
49+
val all = processor.extract(contentChars, true, true).asScala
50+
if (errors.isEmpty) {
51+
52+
def byKind(kind: UsingDirectiveKind) = all.find(_.getKind == kind).get
53+
54+
def getDirectives(directives: UsingDirectives) =
55+
directives.getAst() match {
56+
case ud: UsingDefs =>
57+
ud.getUsingDefs().asScala
58+
case _ =>
59+
Nil
60+
}
61+
62+
val codeDirectives = byKind(UsingDirectiveKind.Code)
63+
val specialCommentDirectives = byKind(UsingDirectiveKind.SpecialComment)
64+
val plainCommentDirectives = byKind(UsingDirectiveKind.PlainComment)
65+
66+
def reportWarning(msg: String, values: Seq[UsingDef], before: Boolean = true): Unit =
67+
values.foreach { v =>
68+
val astPos = v.getPosition()
69+
val (start, end) =
70+
if (before) (0, astPos.getColumn())
71+
else (astPos.getColumn(), astPos.getColumn() + v.getSyntax.getKeyword.size)
72+
val position = Position.File(path, (astPos.getLine(), start), (astPos.getLine(), end))
73+
logger.diagnostic(msg, positions = Seq(position))
74+
}
75+
76+
val usedDirectives =
77+
if (!codeDirectives.getFlattenedMap().isEmpty()) {
78+
val msg =
79+
"This using directive is ignored. File contains directives outside comments and those have higher precedence."
80+
reportWarning(
81+
msg,
82+
getDirectives(plainCommentDirectives) ++ getDirectives(specialCommentDirectives)
83+
)
84+
codeDirectives
85+
}
86+
else if (!specialCommentDirectives.getFlattenedMap().isEmpty()) {
87+
val msg =
88+
s"This using directive is ignored. $changeToSpecialCommentMsg"
89+
reportWarning(msg, getDirectives(plainCommentDirectives))
90+
specialCommentDirectives
91+
}
92+
else {
93+
reportWarning(changeToSpecialCommentMsg, getDirectives(plainCommentDirectives))
94+
plainCommentDirectives
95+
}
96+
97+
// All using directives should use just `using` keyword, no @using or require
98+
reportWarning(
99+
"Deprecated using directive syntax, please use keyword `using`.",
100+
getDirectives(usedDirectives).filter(_.getSyntax() != UsingDirectiveSyntax.Using),
101+
before = false
102+
)
103+
104+
val flattened = usedDirectives.getFlattenedMap.asScala.toSeq
105+
val strictDirectives =
106+
flattened.map {
107+
case (k, l) =>
108+
StrictDirective(k.getPath.asScala.mkString("."), l.asScala.toSeq)
109+
}
110+
111+
val offset =
112+
if (usedDirectives.getKind() != UsingDirectiveKind.Code) 0
113+
else usedDirectives.getCodeOffset()
114+
if (supportedDirectives.contains(usedDirectives.getKind()))
115+
Right(ExtractedDirectives(offset, strictDirectives))
116+
else {
117+
val directiveVales =
118+
usedDirectives.getFlattenedMap.values().asScala.toList.flatMap(_.asScala)
119+
val values = DirectiveUtil.stringValues(directiveVales, path, cwd) ++
120+
DirectiveUtil.numericValues(directiveVales, path, cwd)
121+
Left(new DirectiveErrors(
122+
::(s"Directive '${usedDirectives.getKind}' is not supported in the given context'", Nil),
123+
values.flatMap(_._1.positions)
124+
))
125+
}
126+
}
127+
else {
128+
val errors0 = errors.map(diag => new MalformedDirectiveError(diag.message, diag.positions))
129+
Left(CompositeBuildException(errors0.toSeq))
130+
}
131+
}
132+
}

modules/build/src/main/scala/scala/build/preprocessing/JavaPreprocessor.scala

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
package scala.build.preprocessing
22

3-
import java.nio.charset.StandardCharsets
3+
import com.virtuslab.using_directives.custom.model.UsingDirectiveKind
44

5+
import java.nio.charset.StandardCharsets
6+
import scala.build.EitherCps.{either, value}
57
import scala.build.errors.BuildException
8+
import scala.build.options.BuildRequirements
9+
import scala.build.preprocessing.ExtractedDirectives.from
10+
import scala.build.preprocessing.ScalaPreprocessor._
611
import scala.build.{Inputs, Logger}
712

813
case object JavaPreprocessor extends Preprocessor {
@@ -11,9 +16,32 @@ case object JavaPreprocessor extends Preprocessor {
1116
logger: Logger
1217
): Option[Either[BuildException, Seq[PreprocessedSource]]] =
1318
input match {
14-
case j: Inputs.JavaFile =>
15-
Some(Right(Seq(PreprocessedSource.OnDisk(j.path, None, None, Nil, None))))
16-
19+
case j: Inputs.JavaFile => Some(either {
20+
val content = value(PreprocessingUtil.maybeRead(j.path))
21+
val scopePath = ScopePath.fromPath(j.path)
22+
val ExtractedDirectives(_, directives0) =
23+
value(from(
24+
content.toCharArray,
25+
Right(j.path),
26+
logger,
27+
Array(UsingDirectiveKind.PlainComment, UsingDirectiveKind.SpecialComment),
28+
scopePath
29+
))
30+
val updatedOptions = value(DirectivesProcessor.process(
31+
directives0,
32+
usingDirectiveHandlers,
33+
Right(j.path),
34+
scopePath,
35+
logger
36+
))
37+
Seq(PreprocessedSource.OnDisk(
38+
j.path,
39+
Some(updatedOptions.global),
40+
Some(BuildRequirements()),
41+
Nil,
42+
None
43+
))
44+
})
1745
case v: Inputs.VirtualJavaFile =>
1846
val content = new String(v.content, StandardCharsets.UTF_8)
1947
val s = PreprocessedSource.InMemory(

modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala

Lines changed: 3 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,17 @@
11
package scala.build.preprocessing
22

3-
import com.virtuslab.using_directives.config.Settings
4-
import com.virtuslab.using_directives.custom.model.{
5-
UsingDirectiveKind,
6-
UsingDirectiveSyntax,
7-
UsingDirectives
8-
}
9-
import com.virtuslab.using_directives.custom.utils.ast.{UsingDef, UsingDefs}
10-
import com.virtuslab.using_directives.{Context, UsingDirectivesProcessor}
3+
import com.virtuslab.using_directives.custom.model.UsingDirectiveKind
114
import dependency.AnyDependency
125
import dependency.parser.DependencyParser
136

147
import java.nio.charset.StandardCharsets
15-
168
import scala.build.EitherCps.{either, value}
179
import scala.build.Ops._
1810
import scala.build.errors._
1911
import scala.build.internal.{AmmUtil, Util}
2012
import scala.build.options.{BuildOptions, BuildRequirements, ClassPathOptions, ShadowingSeq}
2113
import scala.build.preprocessing.directives._
2214
import scala.build.{Inputs, Logger, Position, Positioned}
23-
import scala.collection.mutable
24-
import scala.jdk.CollectionConverters._
2515

2616
case object ScalaPreprocessor extends Preprocessor {
2717

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

191181
import fastparse._
182+
192183
import scala.build.internal.ScalaParse._
193184

194185
val res = parse(content, Header(_))
@@ -274,10 +265,9 @@ case object ScalaPreprocessor extends Preprocessor {
274265
cwd: ScopePath,
275266
logger: Logger
276267
): Either[BuildException, StrictDirectivesProcessingOutput] = either {
277-
278268
val contentChars = content.toCharArray
279269
val ExtractedDirectives(codeOffset, directives0) =
280-
value(extractUsingDirectives(contentChars, path, logger))
270+
value(ExtractedDirectives.from(contentChars, path, logger, UsingDirectiveKind.values(), cwd))
281271

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

350-
case class ExtractedDirectives(offset: Int, directives: Seq[StrictDirective])
351-
352340
val changeToSpecialCommentMsg =
353341
"Using directive using plain comments are deprecated. Please use a special comment syntax: '//> ...' or '/*> ... */'"
354342

355-
def extractUsingDirectives(
356-
contentChars: Array[Char],
357-
path: Either[String, os.Path],
358-
logger: Logger
359-
): Either[BuildException, ExtractedDirectives] = {
360-
val errors = new mutable.ListBuffer[Diagnostic]
361-
val reporter = CustomDirectivesReporter.create(path) { diag =>
362-
if (diag.severity == Severity.Warning)
363-
logger.log(Seq(diag))
364-
else
365-
errors += diag
366-
}
367-
val processor = {
368-
val settings = new Settings
369-
settings.setAllowStartWithoutAt(true)
370-
settings.setAllowRequire(false)
371-
val context = new Context(reporter, settings)
372-
new UsingDirectivesProcessor(context)
373-
}
374-
val all = processor.extract(contentChars, true, true).asScala
375-
if (errors.isEmpty) {
376-
377-
def byKind(kind: UsingDirectiveKind) = all.find(_.getKind == kind).get
378-
379-
def getDirectives(directives: UsingDirectives) =
380-
directives.getAst() match {
381-
case ud: UsingDefs =>
382-
ud.getUsingDefs().asScala.toSeq
383-
case _ =>
384-
Nil
385-
}
386-
387-
val codeDirectives = byKind(UsingDirectiveKind.Code)
388-
val specialCommentDirectives = byKind(UsingDirectiveKind.SpecialComment)
389-
val plainCommentDirectives = byKind(UsingDirectiveKind.PlainComment)
390-
391-
def reportWarning(msg: String, values: Seq[UsingDef], before: Boolean = true): Unit =
392-
values.foreach { v =>
393-
val astPos = v.getPosition()
394-
val (start, end) =
395-
if (before) (0, astPos.getColumn())
396-
else (astPos.getColumn(), astPos.getColumn() + v.getSyntax.getKeyword.size)
397-
val position = Position.File(path, (astPos.getLine(), start), (astPos.getLine(), end))
398-
logger.diagnostic(msg, positions = Seq(position))
399-
}
400-
401-
val usedDirectives =
402-
if (!codeDirectives.getFlattenedMap().isEmpty()) {
403-
val msg =
404-
"This using directive is ignored. File contains directives outside comments and those have higher precedence."
405-
reportWarning(
406-
msg,
407-
getDirectives(plainCommentDirectives) ++ getDirectives(specialCommentDirectives)
408-
)
409-
codeDirectives
410-
}
411-
else if (!specialCommentDirectives.getFlattenedMap().isEmpty()) {
412-
val msg =
413-
s"This using directive is ignored. $changeToSpecialCommentMsg"
414-
reportWarning(msg, getDirectives(plainCommentDirectives))
415-
specialCommentDirectives
416-
}
417-
else {
418-
reportWarning(changeToSpecialCommentMsg, getDirectives(plainCommentDirectives))
419-
plainCommentDirectives
420-
}
421-
422-
// All using directives should use just `using` keyword, no @using or require
423-
reportWarning(
424-
"Deprecated using directive syntax, please use keyword `using`.",
425-
getDirectives(usedDirectives).filter(_.getSyntax() != UsingDirectiveSyntax.Using),
426-
before = false
427-
)
428-
429-
val flattened = usedDirectives.getFlattenedMap.asScala.toSeq
430-
val strictDirectives =
431-
flattened.map {
432-
case (k, l) =>
433-
StrictDirective(k.getPath.asScala.mkString("."), l.asScala.toSeq)
434-
}
435-
436-
val offset =
437-
if (usedDirectives.getKind() != UsingDirectiveKind.Code) 0
438-
else usedDirectives.getCodeOffset()
439-
Right(ExtractedDirectives(offset, strictDirectives))
440-
}
441-
else {
442-
val errors0 = errors.map(diag => new MalformedDirectiveError(diag.message, diag.positions))
443-
Left(CompositeBuildException(errors0.toSeq))
444-
}
445-
}
446-
447343
private def parseDependency(str: String, pos: Position): Either[BuildException, AnyDependency] =
448344
DependencyParser.parse(str) match {
449345
case Left(msg) => Left(new DependencyFormatError(str, msg, positionOpt = Some(pos)))

modules/build/src/main/scala/scala/build/preprocessing/directives/RequireScalaVersionDirectiveHandler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ case object RequireScalaVersionDirectiveHandler extends RequireDirectiveHandler
5959
Right(Some(req))
6060
case _ =>
6161
// TODO: Handle errors and conflicts
62-
Left(new DirectiveErrors(::("Match error in ScalaVersionDirectiveHandler", Nil)))
62+
Left(new DirectiveErrors(::("Match error in ScalaVersionDirectiveHandler", Nil), Seq.empty))
6363
}
6464

6565
def handleValues(

modules/build/src/main/scala/scala/build/preprocessing/directives/RequireScopeDirectiveHandler.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ case object RequireScopeDirectiveHandler extends RequireDirectiveHandler {
4040
scope = Some(BuildRequirements.ScopeRequirement(scope))
4141
)
4242
Right(Some(req))
43-
case _ => Left(new DirectiveErrors(::("No such scope", Nil)))
43+
case _ => Left(new DirectiveErrors(::("No such scope", Nil), Seq.empty))
4444
}
4545

4646
val scoped = values
@@ -54,7 +54,7 @@ case object RequireScopeDirectiveHandler extends RequireDirectiveHandler {
5454
)
5555
)
5656
Right(req)
57-
case (_, Some(_)) => Left(new DirectiveErrors(::("No such scope", Nil)))
57+
case (_, Some(_)) => Left(new DirectiveErrors(::("No such scope", Nil), Seq.empty))
5858
}
5959
.toSeq
6060
.sequence

modules/build/src/test/scala/scala/build/tests/ScalaPreprocessorTests.scala

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ package scala.build
22
package tests
33

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

6-
import scala.build.Logger
77
import scala.build.errors.Diagnostic
8-
import scala.build.preprocessing.ScalaPreprocessor
8+
import scala.build.preprocessing.{ExtractedDirectives, ScopePath}
99

1010
class ScalaPreprocessorTests extends munit.FunSuite {
1111

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

4746
val diags = persistentLogger.diagnostics

0 commit comments

Comments
 (0)