Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add KotlinTemplate #219

Merged
merged 14 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/main/java/org/openrewrite/kotlin/KotlinParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,18 @@ public Builder classpathFromResources(ExecutionContext ctx, String... classpath)
return this;
}

/**
* This is an internal API which is subject to removal or change.
*/
public Builder addClasspathEntry(Path classpath) {
if (this.classpath.isEmpty()) {
this.classpath = Collections.singletonList(classpath);
} else {
this.classpath.add(classpath);
}
return this;
}

public Builder typeCache(JavaTypeCache typeCache) {
this.typeCache = typeCache;
return this;
Expand Down
123 changes: 123 additions & 0 deletions src/main/java/org/openrewrite/kotlin/KotlinTemplate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* 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.openrewrite.Cursor;
import org.openrewrite.internal.StringUtils;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.internal.template.Substitutions;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaCoordinates;
import org.openrewrite.kotlin.internal.template.KotlinSubstitutions;
import org.openrewrite.kotlin.internal.template.KotlinTemplateParser;

import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;

public class KotlinTemplate extends JavaTemplate {
private KotlinTemplate(boolean contextSensitive, KotlinParser.Builder parser, String code, Set<String> imports, Consumer<String> onAfterVariableSubstitution, Consumer<String> onBeforeParseTemplate) {
super(
code,
onAfterVariableSubstitution,
new KotlinTemplateParser(
contextSensitive,
augmentClasspath(parser),
onAfterVariableSubstitution,
onBeforeParseTemplate,
imports
)
);
}

private static KotlinParser.Builder augmentClasspath(KotlinParser.Builder parserBuilder) {
return parserBuilder.addClasspathEntry(getTemplateClasspathDir());
}

@Override
protected Substitutions substitutions(Object[] parameters) {
return new KotlinSubstitutions(getCode(), parameters);
}

public static <J2 extends J> J2 apply(String template, Cursor scope, JavaCoordinates coordinates, Object... parameters) {
return builder(template).build().apply(scope, coordinates, parameters);
}

public static Builder builder(String code) {
return new Builder(code);
}

public static boolean matches(String template, Cursor cursor) {
return builder(template).build().matches(cursor);
}

@SuppressWarnings("unused")
public static class Builder extends JavaTemplate.Builder {

private final String code;
private final Set<String> imports = new HashSet<>();

private KotlinParser.Builder parser = KotlinParser.builder();

private Consumer<String> onAfterVariableSubstitution = s -> {
};
private Consumer<String> onBeforeParseTemplate = s -> {
};

Builder(String code) {
super(code);
this.code = code;
}

public Builder imports(String... fullyQualifiedTypeNames) {
for (String typeName : fullyQualifiedTypeNames) {
validateImport(typeName);
this.imports.add("import " + typeName + "\n");
}
return this;
}

private void validateImport(String typeName) {
if (StringUtils.isBlank(typeName)) {
throw new IllegalArgumentException("Imports must not be blank");
} else if (typeName.startsWith("import ")) {
throw new IllegalArgumentException("Imports are expressed as fully-qualified names and should not include an \"import \" prefix");
} else if (typeName.endsWith(";") || typeName.endsWith("\n")) {
throw new IllegalArgumentException("Imports are expressed as fully-qualified names and should not include a suffixed terminator");
}
}

Builder parser(KotlinParser.Builder parser) {
this.parser = parser;
return this;
}

public Builder doAfterVariableSubstitution(Consumer<String> afterVariableSubstitution) {
this.onAfterVariableSubstitution = afterVariableSubstitution;
return this;
}

public Builder doBeforeParseTemplate(Consumer<String> beforeParseTemplate) {
this.onBeforeParseTemplate = beforeParseTemplate;
return this;
}

public KotlinTemplate build() {
return new KotlinTemplate(false, parser, code, imports,
onAfterVariableSubstitution, onBeforeParseTemplate);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,16 @@
import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.java.MethodMatcher;
import org.openrewrite.java.tree.J;
import org.openrewrite.kotlin.KotlinParser;
import org.openrewrite.kotlin.KotlinTemplate;
import org.openrewrite.kotlin.KotlinVisitor;
import org.openrewrite.kotlin.tree.K;


@Value
@EqualsAndHashCode(callSuper = false)
public class ReplaceCharToIntWithCode extends Recipe {
private static final MethodMatcher CHAR_TO_INT_METHOD_MATCHER = new MethodMatcher("kotlin.Char toInt()");
@Nullable
private static J.FieldAccess charCodeTemplate;

@Override
public String getDisplayName() {
Expand All @@ -51,29 +47,16 @@ public String getDescription() {
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new KotlinVisitor<ExecutionContext>() {
@Override
public J visitMethodInvocation(J.MethodInvocation method,
ExecutionContext ctx) {
public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
if (CHAR_TO_INT_METHOD_MATCHER.matches(method) && method.getSelect() != null) {
J.FieldAccess codeTemplate = getCharCodeTemplate();
return codeTemplate.withTarget(method.getSelect()).withPrefix(method.getPrefix());
return KotlinTemplate.builder("#{any(Char)}.code")
.build()
.apply(getCursor(), method.getCoordinates().replace(), method.getSelect())
.withPrefix(method.getPrefix());
}
return super.visitMethodInvocation(method, ctx);
}
};
}

@SuppressWarnings("all")
private static J.FieldAccess getCharCodeTemplate() {
if (charCodeTemplate == null) {
K.CompilationUnit kcu = KotlinParser.builder().build()
.parse("fun method(c : Char) {c.code}")
.map(K.CompilationUnit.class::cast)
.findFirst()
.get();

charCodeTemplate = (J.FieldAccess) ((J.MethodDeclaration) kcu.getStatements().get(0)).getBody().getStatements().get(0);
}

return charCodeTemplate;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.internal.template;

import org.openrewrite.Cursor;
import org.openrewrite.java.internal.template.BlockStatementTemplateGenerator;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaCoordinates;

import java.util.Set;

public class KotlinBlockStatementTemplateGenerator extends BlockStatementTemplateGenerator {
public KotlinBlockStatementTemplateGenerator(Set<String> imports, boolean contextSensitive) {
super(imports, contextSensitive);
}

@Override
protected void contextFreeTemplate(Cursor cursor, J j, StringBuilder before, StringBuilder after) {
if (!(j instanceof Expression)) {
throw new IllegalArgumentException(
"Kotlin templating is currently only implemented for context-free expressions and not for `" + j.getClass() + "` instances.");
}

before.insert(0, "class Template {\n");
before.append("var o : Object = ");
after.append(";");
after.append("\n}");

before.insert(0, TEMPLATE_INTERNAL_IMPORTS);
for (String anImport : imports) {
before.insert(0, anImport);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* 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.internal.template;

import org.openrewrite.java.internal.template.Substitutions;
import org.openrewrite.java.tree.JavaType;

public class KotlinSubstitutions extends Substitutions {
public KotlinSubstitutions(String code, Object[] parameters) {
super(code, parameters);
}

@Override
protected String newObjectParameter(String fqn, int index) {
return "__P__./*__p" + index + "__*/p<" + fqn + ">()";
}

@Override
protected String newPrimitiveParameter(String fqn, int index) {
return newObjectParameter(fqn, index);
}

@Override
protected String newArrayParameter(JavaType elemType, int dimensions, int index) {
// generate literal of the form: `arrayOf(arrayOf<String?>())`
StringBuilder builder = new StringBuilder("/*__p" + index + "__*/");
for (int i = 0; i < dimensions; i++) {
builder.append("arrayOf");
if (i < dimensions - 1) {
builder.append('(');
}
}
builder.append('<');
if (elemType instanceof JavaType.Primitive) {
builder.append(((JavaType.Primitive) elemType).getKeyword());
} else if (elemType instanceof JavaType.FullyQualified) {
builder.append(((JavaType.FullyQualified) elemType).getFullyQualifiedName().replace("$", "."));
}
builder.append(">(");
for (int i = 0; i < dimensions; i++) {
builder.append(')');
}
return builder.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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.internal.template;

import org.openrewrite.java.internal.template.AnnotationTemplateGenerator;
import org.openrewrite.java.internal.template.JavaTemplateParser;
import org.openrewrite.kotlin.KotlinParser;

import java.util.Set;
import java.util.function.Consumer;

public class KotlinTemplateParser extends JavaTemplateParser {
public KotlinTemplateParser(boolean contextSensitive, KotlinParser.Builder parser, Consumer<String> onAfterVariableSubstitution, Consumer<String> onBeforeParseTemplate, Set<String> imports) {
super(
parser,
onAfterVariableSubstitution,
onBeforeParseTemplate,
imports,
contextSensitive,
new KotlinBlockStatementTemplateGenerator(imports, contextSensitive),
new AnnotationTemplateGenerator(imports)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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.
*/
@NonNullApi
@NonNullFields
package org.openrewrite.kotlin.internal.template;

import org.openrewrite.internal.lang.NonNullApi;
import org.openrewrite.internal.lang.NonNullFields;
21 changes: 21 additions & 0 deletions src/main/java/org/openrewrite/kotlin/marker/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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.
*/
@NonNullApi
@NonNullFields
package org.openrewrite.kotlin.marker;

import org.openrewrite.internal.lang.NonNullApi;
import org.openrewrite.internal.lang.NonNullFields;
Loading