Skip to content

Commit

Permalink
Add .dependsOn method for KotlinParser (#617)
Browse files Browse the repository at this point in the history
* Add .dependsOn method for KotlinParser

* Add .dependsOn method for KotlinParser

* Add basic testcase

* Fix up sourcePath determine logic

* Minor polish

---------

Co-authored-by: Tiemen Schut <[email protected]>
Co-authored-by: Tim te Beek <[email protected]>
  • Loading branch information
3 people authored Nov 18, 2024
1 parent 749f1f6 commit a467f7c
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 5 deletions.
47 changes: 42 additions & 5 deletions src/main/java/org/openrewrite/kotlin/KotlinParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import org.jetbrains.kotlin.utils.PathUtil;
import org.jspecify.annotations.Nullable;
import org.openrewrite.*;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.internal.JavaTypeCache;
import org.openrewrite.java.marker.JavaSourceSet;
Expand All @@ -87,7 +88,6 @@
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.Collections.emptyList;
Expand All @@ -101,6 +101,7 @@
import static org.jetbrains.kotlin.config.JVMConfigurationKeys.DO_NOT_CLEAR_BINDING_CONTEXT;
import static org.jetbrains.kotlin.config.JVMConfigurationKeys.LINK_VIA_SIGNATURES;
import static org.jetbrains.kotlin.incremental.IncrementalFirJvmCompilerRunnerKt.configureBaseRoots;
import static org.openrewrite.kotlin.KotlinParser.SourcePathFromSourceTextResolver.determinePath;

@SuppressWarnings("CommentedOutCode")
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
Expand All @@ -115,6 +116,9 @@ public class KotlinParser implements Parser {
@Nullable
private final Collection<Path> classpath;

@Nullable
private final List<Input> dependsOn;

private final List<NamedStyles> styles;
private final boolean logCompilationWarningsAndErrors;
private final JavaTypeCache typeCache;
Expand Down Expand Up @@ -162,10 +166,11 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path re
// TODO: FIR and disposable may not be necessary using the IR.
Disposable disposable = Disposer.newDisposable();
CompiledSource compilerCus;
List<Input> acceptedInputs = ListUtils.concatAll(dependsOn, acceptedInputs(sources).collect(toList()));
try {
compilerCus = parse(acceptedInputs(sources).collect(Collectors.toList()), disposable, pctx);
compilerCus = parse(acceptedInputs, disposable, pctx);
} catch (Exception e) {
return acceptedInputs(sources).map(input -> ParseError.build(this, input, relativeTo, ctx, e));
return acceptedInputs.stream().map(input -> ParseError.build(this, input, relativeTo, ctx, e));
}

FirSession firSession = compilerCus.getFirSession();
Expand Down Expand Up @@ -201,7 +206,8 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path re
return (SourceFile) null;
})
.limit(1))
.filter(Objects::nonNull);
.filter(Objects::nonNull)
.filter(source -> !source.getSourcePath().getFileName().toString().startsWith("dependsOn-"));
}

@Override
Expand Down Expand Up @@ -252,6 +258,7 @@ public static class Builder extends Parser.Builder {
@Nullable
private Collection<Path> classpath = emptyList();

private List<Input> dependsOn = emptyList();
private JavaTypeCache typeCache = new JavaTypeCache();
private boolean logCompilationWarningsAndErrors;
private final List<NamedStyles> styles = new ArrayList<>();
Expand Down Expand Up @@ -303,6 +310,13 @@ public Builder addClasspathEntry(Path classpath) {
return this;
}

public Builder dependsOn(@Language("kotlin") String... inputsAsStrings) {
this.dependsOn = Arrays.stream(inputsAsStrings)
.map(input -> Input.fromString(determinePath("dependsOn-", input), input))
.collect(toList());
return this;
}

public Builder typeCache(JavaTypeCache typeCache) {
this.typeCache = typeCache;
return this;
Expand Down Expand Up @@ -335,7 +349,7 @@ public Builder languageLevel(KotlinLanguageLevel languageLevel) {

@Override
public KotlinParser build() {
return new KotlinParser(resolvedClasspath(), styles, logCompilationWarningsAndErrors, typeCache, moduleName, languageLevel, isKotlinScript);
return new KotlinParser(resolvedClasspath(), dependsOn, styles, logCompilationWarningsAndErrors, typeCache, moduleName, languageLevel, isKotlinScript);
}

@Override
Expand Down Expand Up @@ -609,4 +623,27 @@ private List<Integer> getCRLFLocations(String source) {

return cRLFIndices;
}

static class SourcePathFromSourceTextResolver {
private static final Pattern packagePattern = Pattern.compile("^package\\s+(\\S+)");
private static final Pattern classPattern = Pattern.compile("(class|interface|enum class)\\s*(<[^>]*>)?\\s+(\\w+)");
private static final Pattern publicClassPattern = Pattern.compile("public\\s+" + classPattern.pattern());

private static Optional<String> matchClassPattern(Pattern pattern, String source) {
Matcher classMatcher = pattern.matcher(source);
if (classMatcher.find()) {
return Optional.of(classMatcher.group(3));
}
return Optional.empty();
}

static Path determinePath(String prefix, String sourceCode) {
String className = matchClassPattern(publicClassPattern, sourceCode)
.orElseGet(() -> matchClassPattern(classPattern, sourceCode)
.orElse(Long.toString(System.nanoTime())));
Matcher packageMatcher = packagePattern.matcher(sourceCode);
String pkg = packageMatcher.find() ? packageMatcher.group(1).replace('.', '/') + "/" : "";
return Paths.get(pkg, prefix + className + ".kt");
}
}
}
43 changes: 43 additions & 0 deletions src/test/java/org/openrewrite/kotlin/KotlinParserTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.kotlin;

import org.junit.jupiter.api.Test;
import org.openrewrite.test.RewriteTest;

import static org.openrewrite.kotlin.Assertions.kotlin;

class KotlinParserTest implements RewriteTest {

@Test
void classDefinitionFromDependsOn() {
rewriteRun(
spec -> spec.parser(KotlinParser.builder().dependsOn("""
package foo.bar
class MyClass
""")),
kotlin(
"""
import foo.bar.MyClass
val myClass: MyClass? = null
"""
)
);
}

}

0 comments on commit a467f7c

Please sign in to comment.