/* * Copyright 2013 the original author or authors. * * 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * 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.gradle.plugin.use.internal; import org.codehaus.groovy.ast.expr.ClosureExpression; import org.codehaus.groovy.ast.expr.ConstantExpression; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.MethodCallExpression; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.ast.stmt.ExpressionStatement; import org.codehaus.groovy.ast.stmt.Statement; import org.codehaus.groovy.control.SourceUnit; import org.gradle.api.internal.DocumentationRegistry; import org.gradle.groovy.scripts.ScriptSource; import org.gradle.groovy.scripts.internal.RestrictiveCodeVisitor; import org.gradle.groovy.scripts.internal.ScriptBlock; import org.gradle.plugin.internal.InvalidPluginIdException; import org.gradle.plugin.management.internal.PluginRequests; import org.gradle.plugin.use.PluginDependencySpec; import static org.gradle.groovy.scripts.internal.AstUtils.hasSingleConstantArgOfType; import static org.gradle.groovy.scripts.internal.AstUtils.isOfType; public class PluginUseScriptBlockMetadataExtractor { public static final String NEED_SINGLE_BOOLEAN = "argument list must be exactly 1 literal boolean"; public static final String NEED_SINGLE_STRING = "argument list must be exactly 1 literal non empty string"; public static final String BASE_MESSAGE = "only id(String) method calls allowed in plugins {} script block"; public static final String EXTENDED_MESSAGE = "only version(String) and apply(boolean) method calls allowed in plugins {} script block"; private static final String NOT_LITERAL_METHOD_NAME = "method name must be literal (i.e. not a variable)"; private static final String NOT_LITERAL_ID_METHOD_NAME = BASE_MESSAGE + " - " + NOT_LITERAL_METHOD_NAME; private final DocumentationRegistry documentationRegistry; private final PluginRequestCollector pluginRequestCollector; public PluginUseScriptBlockMetadataExtractor(ScriptSource scriptSource, DocumentationRegistry documentationRegistry) { this.pluginRequestCollector = new PluginRequestCollector(scriptSource); this.documentationRegistry = documentationRegistry; } public void extract(SourceUnit sourceUnit, ScriptBlock scriptBlock) { ClosureExpression closureArg = scriptBlock.getClosureExpression(); closureArg.getCode().visit(new RestrictiveCodeVisitor(sourceUnit, formatErrorMessage(BASE_MESSAGE)) { @Override public void visitBlockStatement(BlockStatement block) { for (Statement statement : block.getStatements()) { statement.visit(this); } } @Override public void visitMethodCallExpression(MethodCallExpression call) { if (!call.isImplicitThis()) { Expression target = call.getObjectExpression(); if (!(target instanceof MethodCallExpression)) { restrict(target, formatErrorMessage(BASE_MESSAGE)); return; } visitMethodCallExpression((MethodCallExpression) target); } if (call.getMethod() instanceof ConstantExpression) { ConstantExpression methodName = (ConstantExpression) call.getMethod(); if (isOfType(methodName, String.class)) { String methodNameText = methodName.getText(); if (methodNameText.equals("id") || methodNameText.equals("version")) { ConstantExpression argumentExpression = hasSingleConstantArgOfType(call, String.class); if (argumentExpression == null) { restrict(call, formatErrorMessage(NEED_SINGLE_STRING)); return; } String argStringValue = argumentExpression.getValue().toString(); if (argStringValue.length() == 0) { restrict(argumentExpression, formatErrorMessage(NEED_SINGLE_STRING)); return; } if (methodName.getText().equals("id")) { if (call.isImplicitThis()) { try { DefaultPluginId.validate(argStringValue); call.setNodeMetaData(PluginDependencySpec.class, pluginRequestCollector.createSpec(call.getLineNumber()).id(argStringValue)); } catch (InvalidPluginIdException e) { restrict(argumentExpression, formatErrorMessage(e.getReason())); } } else { restrict(call, formatErrorMessage(BASE_MESSAGE)); } } if (methodName.getText().equals("version")) { PluginDependencySpec spec = getSpecFor(call); if (spec == null) { return; } spec.version(argStringValue); call.setNodeMetaData(PluginDependencySpec.class, spec); } } else if (methodNameText.equals("apply")) { ConstantExpression arguments = hasSingleConstantArgOfType(call, boolean.class); if (arguments == null) { restrict(call, formatErrorMessage(NEED_SINGLE_BOOLEAN)); return; } PluginDependencySpec spec = getSpecFor(call); if (spec == null) { return; } spec.apply((Boolean) arguments.getValue()); } else { if (!call.isImplicitThis()) { restrict(methodName, formatErrorMessage(EXTENDED_MESSAGE)); } else { restrict(methodName, formatErrorMessage(BASE_MESSAGE)); } } } else { restrict(methodName, formatErrorMessage(NOT_LITERAL_ID_METHOD_NAME)); } } else { restrict(call); } } private PluginDependencySpec getSpecFor(MethodCallExpression call) { Expression objectExpression = call.getObjectExpression(); if (objectExpression instanceof MethodCallExpression) { return objectExpression.getNodeMetaData(PluginDependencySpec.class); } else { restrict(call, formatErrorMessage(BASE_MESSAGE)); return null; } } @Override public void visitExpressionStatement(ExpressionStatement statement) { statement.getExpression().visit(this); } }); } public PluginRequests getPluginRequests() { return pluginRequestCollector.getPluginRequests(); } public String formatErrorMessage(String message) { return String.format("%s%n%nSee %s for information on the plugins {} block%n%n", message, documentationRegistry.getDocumentationFor("plugins", "sec:plugins_block")); } }