Skip to content

Commit e9402bf

Browse files
authored
Merge pull request #1591 from wleczny/multiple-using-directives-handling
Warning for multiple files with using directives
2 parents 5604ed5 + 414321e commit e9402bf

File tree

15 files changed

+234
-74
lines changed

15 files changed

+234
-74
lines changed

build.sc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import $file.project.settings, settings.{
1616
ScalaCliTests,
1717
localRepoResourcePath,
1818
platformExecutableJarExtension,
19-
workspaceDirName
19+
workspaceDirName,
20+
projectFileName
2021
}
2122
import $file.project.deps, deps.customRepositories
2223
import $file.project.website
@@ -385,6 +386,7 @@ trait Core extends ScalaCliSbtModule with ScalaCliPublishModule with HasTests
385386
| def defaultScala213Version = "${Scala.scala213}"
386387
|
387388
| def workspaceDirName = "$workspaceDirName"
389+
| def projectFileName = "$projectFileName"
388390
|
389391
| def defaultGraalVMJavaVersion = ${deps.graalVmJavaVersion}
390392
| def defaultGraalVMVersion = "${deps.graalVmVersion}"

modules/build/src/main/scala/scala/build/CrossSources.scala

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import scala.build.Positioned
66
import scala.build.errors.{BuildException, CompositeBuildException, MalformedDirectiveError}
77
import scala.build.input.ElementsUtils.*
88
import scala.build.input.*
9+
import scala.build.internal.Constants
910
import scala.build.options.{
1011
BuildOptions,
1112
BuildRequirements,
@@ -166,6 +167,29 @@ object CrossSources {
166167
val preprocessedSources =
167168
(preprocessedInputFromArgs ++ preprocessedSourcesFromDirectives).distinct
168169

170+
val preprocessedWithUsingDirs = preprocessedSources.filter(_.directivesPositions.isDefined)
171+
if (preprocessedWithUsingDirs.length > 1) {
172+
val projectFilePath = inputs.elements.projectSettingsFiles.headOption match
173+
case Some(s) => s.path
174+
case _ => inputs.workspace / Constants.projectFileName
175+
preprocessedWithUsingDirs
176+
.filter(_.scopePath != ScopePath.fromPath(projectFilePath))
177+
.foreach { source =>
178+
source.directivesPositions match
179+
case Some(positions) =>
180+
val pos = Seq(
181+
positions.codeDirectives,
182+
positions.specialCommentDirectives,
183+
positions.plainCommentDirectives
184+
).maxBy(p => p.endPos._1)
185+
logger.diagnostic(
186+
s"Using directives detected in multiple files. It is recommended to keep them centralized in the $projectFilePath file.",
187+
positions = Seq(pos)
188+
)
189+
case _ => ()
190+
}
191+
}
192+
169193
val scopedRequirements = preprocessedSources.flatMap(_.scopedRequirements)
170194
val scopedRequirementsByRoot = scopedRequirements.groupBy(_.path.root)
171195
def baseReqs(path: ScopePath): BuildRequirements = {
@@ -238,7 +262,7 @@ object CrossSources {
238262
lazy val subPath = sourcePath.subRelativeTo(dir)
239263
if (os.isDir(sourcePath))
240264
Right(Directory(sourcePath).singleFilesFromDirectory(enableMarkdown))
241-
else if (sourcePath == os.sub / "project.scala")
265+
else if (sourcePath == os.sub / Constants.projectFileName)
242266
Right(Seq(ProjectScalaFile(dir, subPath)))
243267
else if (sourcePath.ext == "scala") Right(Seq(SourceScalaFile(dir, subPath)))
244268
else if (sourcePath.ext == "sc") Right(Seq(Script(dir, subPath)))

modules/build/src/main/scala/scala/build/input/ElementsUtils.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import java.nio.charset.StandardCharsets
55
import java.security.MessageDigest
66

77
import scala.build.Directories
8+
import scala.build.internal.Constants
89

910
object ElementsUtils {
1011
extension (d: Directory) {
@@ -15,7 +16,7 @@ object ElementsUtils {
1516
.collect {
1617
case p if p.last.endsWith(".java") =>
1718
JavaFile(d.path, p.subRelativeTo(d.path))
18-
case p if p.last == "project.scala" =>
19+
case p if p.last == Constants.projectFileName =>
1920
ProjectScalaFile(d.path, p.subRelativeTo(d.path))
2021
case p if p.last.endsWith(".scala") =>
2122
SourceScalaFile(d.path, p.subRelativeTo(d.path))
@@ -31,8 +32,8 @@ object ElementsUtils {
3132
}
3233

3334
def configFile: Seq[ProjectScalaFile] =
34-
if (os.exists(d.path / "project.scala"))
35-
Seq(ProjectScalaFile(d.path, os.sub / "project.scala"))
35+
if (os.exists(d.path / Constants.projectFileName))
36+
Seq(ProjectScalaFile(d.path, os.sub / Constants.projectFileName))
3637
else Nil
3738
}
3839

modules/build/src/main/scala/scala/build/input/Inputs.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ object Inputs {
139139
settingsFiles.headOption.map { s =>
140140
if (settingsFiles.length > 1)
141141
System.err.println(
142-
s"Warning: more than one project.scala file has been found. Setting ${s.base} as the project root directory for this run."
142+
s"Warning: more than one ${Constants.projectFileName} file has been found. Setting ${s.base} as the project root directory for this run."
143143
)
144144
(s.base, true, WorkspaceOrigin.SourcePaths)
145145
}.orElse {
@@ -282,7 +282,7 @@ object Inputs {
282282
else List(Virtual(url, urlContent))
283283
}
284284
}
285-
else if path.last == "project.scala" then Right(Seq(ProjectScalaFile(dir, subPath)))
285+
else if path.last == Constants.projectFileName then Right(Seq(ProjectScalaFile(dir, subPath)))
286286
else if arg.endsWith(".sc") then Right(Seq(Script(dir, subPath)))
287287
else if arg.endsWith(".scala") then Right(Seq(SourceScalaFile(dir, subPath)))
288288
else if arg.endsWith(".java") then Right(Seq(JavaFile(dir, subPath)))

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

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ package scala.build.preprocessing
22

33
import java.nio.charset.StandardCharsets
44

5+
import scala.build.EitherCps.{either, value}
56
import scala.build.Logger
67
import scala.build.errors.BuildException
78
import scala.build.input.{Inputs, SingleElement, VirtualData}
9+
import scala.build.options.BuildRequirements
10+
import scala.build.preprocessing.PreprocessingUtil.optionsAndPositionsFromDirectives
811

912
case object DataPreprocessor extends Preprocessor {
1013
def preprocess(
@@ -15,23 +18,36 @@ case object DataPreprocessor extends Preprocessor {
1518
): Option[Either[BuildException, Seq[PreprocessedSource]]] =
1619
input match {
1720
case file: VirtualData =>
18-
val content = new String(file.content, StandardCharsets.UTF_8)
21+
val res = either {
22+
val content = new String(file.content, StandardCharsets.UTF_8)
23+
val (updatedOptions, directivesPositions) = value {
24+
optionsAndPositionsFromDirectives(
25+
content,
26+
file.scopePath,
27+
Left(file.subPath.toString),
28+
logger,
29+
maybeRecoverOnError,
30+
allowRestrictedFeatures
31+
)
32+
}
1933

20-
val inMemory = Seq(
21-
PreprocessedSource.InMemory(
22-
Left(file.source),
23-
file.subPath,
24-
content,
25-
0,
26-
None,
27-
None,
28-
Nil,
29-
None,
30-
file.scopePath
34+
val inMemory = Seq(
35+
PreprocessedSource.InMemory(
36+
Left(file.source),
37+
file.subPath,
38+
content,
39+
0,
40+
Some(updatedOptions.global),
41+
Some(BuildRequirements()),
42+
Nil,
43+
None,
44+
file.scopePath,
45+
directivesPositions
46+
)
3147
)
32-
)
33-
34-
Some(Right(inMemory))
48+
inMemory
49+
}
50+
Some(res)
3551
case _ =>
3652
None
3753
}

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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,23 @@ import scala.jdk.CollectionConverters.*
1818

1919
case class ExtractedDirectives(
2020
offset: Int,
21-
directives: Seq[StrictDirective]
21+
directives: Seq[StrictDirective],
22+
positions: Option[DirectivesPositions]
2223
) {
2324
@targetName("append")
2425
def ++(other: ExtractedDirectives): ExtractedDirectives =
25-
ExtractedDirectives(offset, directives ++ other.directives)
26+
ExtractedDirectives(offset, directives ++ other.directives, positions)
2627
}
2728

29+
case class DirectivesPositions(
30+
codeDirectives: Position.File,
31+
specialCommentDirectives: Position.File,
32+
plainCommentDirectives: Position.File
33+
)
34+
2835
object ExtractedDirectives {
2936

30-
def empty: ExtractedDirectives = ExtractedDirectives(0, Seq.empty)
37+
def empty: ExtractedDirectives = ExtractedDirectives(0, Seq.empty, None)
3138

3239
val changeToSpecialCommentMsg =
3340
"Using directive using plain comments are deprecated. Please use a special comment syntax: '//> ...' or '/*> ... */'"
@@ -73,10 +80,21 @@ object ExtractedDirectives {
7380
Nil
7481
}
7582

83+
def getPosition(directives: UsingDirectives) =
84+
val line = directives.getAst().getPosition().getLine()
85+
val column = directives.getAst().getPosition().getColumn()
86+
Position.File(path, (0, 0), (line, column))
87+
7688
val codeDirectives = byKind(UsingDirectiveKind.Code)
7789
val specialCommentDirectives = byKind(UsingDirectiveKind.SpecialComment)
7890
val plainCommentDirectives = byKind(UsingDirectiveKind.PlainComment)
7991

92+
val directivesPositions = DirectivesPositions(
93+
getPosition(codeDirectives),
94+
getPosition(specialCommentDirectives),
95+
getPosition(plainCommentDirectives)
96+
)
97+
8098
def reportWarning(msg: String, values: Seq[UsingDef], before: Boolean = true): Unit =
8199
values.foreach { v =>
82100
val astPos = v.getPosition
@@ -126,7 +144,7 @@ object ExtractedDirectives {
126144
if (usedDirectives.getKind != UsingDirectiveKind.Code) 0
127145
else usedDirectives.getCodeOffset
128146
if (supportedDirectives.contains(usedDirectives.getKind))
129-
Right(ExtractedDirectives(offset, strictDirectives))
147+
Right(ExtractedDirectives(offset, strictDirectives, Some(directivesPositions)))
130148
else {
131149
val directiveVales =
132150
usedDirectives.getFlattenedMap.values().asScala.toList.flatMap(_.asScala)

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

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import scala.build.input.{Inputs, JavaFile, SingleElement, VirtualJavaFile}
1313
import scala.build.internal.JavaParserProxyMaker
1414
import scala.build.options.BuildRequirements
1515
import scala.build.preprocessing.ExtractedDirectives.from
16+
import scala.build.preprocessing.PreprocessingUtil.optionsAndPositionsFromDirectives
1617
import scala.build.preprocessing.ScalaPreprocessor._
1718

1819
/** Java source preprocessor.
@@ -42,29 +43,23 @@ final case class JavaPreprocessor(
4243
case j: JavaFile => Some(either {
4344
val content = value(PreprocessingUtil.maybeRead(j.path))
4445
val scopePath = ScopePath.fromPath(j.path)
45-
val ExtractedDirectives(_, directives0) =
46-
value(from(
47-
content.toCharArray,
46+
val (updatedOptions, directivesPositions) = value {
47+
optionsAndPositionsFromDirectives(
48+
content,
49+
scopePath,
4850
Right(j.path),
4951
logger,
50-
Array(UsingDirectiveKind.PlainComment, UsingDirectiveKind.SpecialComment),
51-
scopePath,
52-
maybeRecoverOnError
53-
))
54-
val updatedOptions = value(DirectivesProcessor.process(
55-
directives0,
56-
usingDirectiveHandlers,
57-
Right(j.path),
58-
scopePath,
59-
logger,
60-
allowRestrictedFeatures
61-
))
52+
maybeRecoverOnError,
53+
allowRestrictedFeatures
54+
)
55+
}
6256
Seq(PreprocessedSource.OnDisk(
6357
j.path,
6458
Some(updatedOptions.global),
6559
Some(BuildRequirements()),
6660
Nil,
67-
None
61+
None,
62+
directivesPositions
6863
))
6964
})
7065
case v: VirtualJavaFile =>
@@ -88,16 +83,27 @@ final case class JavaPreprocessor(
8883
}
8984
else v.subPath
9085
val content = new String(v.content, StandardCharsets.UTF_8)
86+
val (updatedOptions, directivesPositions) = value {
87+
optionsAndPositionsFromDirectives(
88+
content,
89+
v.scopePath,
90+
Left(relPath.toString),
91+
logger,
92+
maybeRecoverOnError,
93+
allowRestrictedFeatures
94+
)
95+
}
9196
val s = PreprocessedSource.InMemory(
9297
originalPath = Left(v.source),
9398
relPath = relPath,
9499
code = content,
95100
ignoreLen = 0,
96-
options = None,
97-
requirements = None,
101+
options = Some(updatedOptions.global),
102+
requirements = Some(BuildRequirements()),
98103
scopedRequirements = Nil,
99104
mainClassOpt = None,
100-
scopePath = v.scopePath
105+
scopePath = v.scopePath,
106+
directivesPositions = directivesPositions
101107
)
102108
Seq(s)
103109
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ case object MarkdownPreprocessor extends Preprocessor {
8484
maybeRecoverOnError = maybeRecoverOnError,
8585
allowRestrictedFeatures = allowRestrictedFeatures
8686
)
87-
}.getOrElse(ProcessingOutput(BuildRequirements(), Nil, BuildOptions(), None))
87+
}.getOrElse(ProcessingOutput(BuildRequirements(), Nil, BuildOptions(), None, None))
8888
val processedCode = processingOutput.updatedContent.getOrElse(wrappedMarkdown.code)
8989
PreprocessedSource.InMemory(
9090
originalPath = reportingPath.map(subPath -> _),
@@ -95,7 +95,8 @@ case object MarkdownPreprocessor extends Preprocessor {
9595
requirements = Some(processingOutput.globalReqs),
9696
processingOutput.scopedReqs,
9797
mainClassOpt = None,
98-
scopePath = scopePath
98+
scopePath = scopePath,
99+
directivesPositions = processingOutput.directivesPositions
99100
)
100101
}
101102
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ sealed abstract class PreprocessedSource extends Product with Serializable {
99

1010
def scopedRequirements: Seq[Scoped[BuildRequirements]]
1111
def scopePath: ScopePath
12+
def directivesPositions: Option[DirectivesPositions]
1213
}
1314

1415
object PreprocessedSource {
@@ -18,7 +19,8 @@ object PreprocessedSource {
1819
options: Option[BuildOptions],
1920
requirements: Option[BuildRequirements],
2021
scopedRequirements: Seq[Scoped[BuildRequirements]],
21-
mainClassOpt: Option[String]
22+
mainClassOpt: Option[String],
23+
directivesPositions: Option[DirectivesPositions]
2224
) extends PreprocessedSource {
2325
def scopePath: ScopePath =
2426
ScopePath.fromPath(path)
@@ -32,7 +34,8 @@ object PreprocessedSource {
3234
requirements: Option[BuildRequirements],
3335
scopedRequirements: Seq[Scoped[BuildRequirements]],
3436
mainClassOpt: Option[String],
35-
scopePath: ScopePath
37+
scopePath: ScopePath,
38+
directivesPositions: Option[DirectivesPositions]
3639
) extends PreprocessedSource {
3740
def reportingPath: Either[String, os.Path] =
3841
originalPath.map(_._2)
@@ -46,6 +49,7 @@ object PreprocessedSource {
4649
def mainClassOpt: None.type = None
4750
def scopePath: ScopePath =
4851
ScopePath.fromPath(path)
52+
def directivesPositions: None.type = None
4953
}
5054

5155
private def index(s: PreprocessedSource): Int =

0 commit comments

Comments
 (0)