509 lines
18 KiB
Java
509 lines
18 KiB
Java
/*
|
|
* Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
package jdk.jshell;
|
|
|
|
import java.util.Collection;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
|
|
/**
|
|
* Provides analysis utilities for source code input.
|
|
* Optional functionality that provides for a richer interactive experience.
|
|
* Includes completion analysis:
|
|
* Is the input a complete snippet of code?
|
|
* Do I need to prompt for more input?
|
|
* Would adding a semicolon make it complete?
|
|
* Is there more than one snippet?
|
|
* etc.
|
|
* Also includes completion suggestions, as might be used in tab-completion.
|
|
*
|
|
* @since 9
|
|
*/
|
|
public abstract class SourceCodeAnalysis {
|
|
|
|
/**
|
|
* Given an input string, find the first snippet of code (one statement,
|
|
* definition, import, or expression) and evaluate if it is complete.
|
|
* @param input the input source string
|
|
* @return a CompletionInfo instance with location and completeness info
|
|
*/
|
|
public abstract CompletionInfo analyzeCompletion(String input);
|
|
|
|
/**
|
|
* Compute possible follow-ups for the given input.
|
|
* Uses information from the current {@code JShell} state, including
|
|
* type information, to filter the suggestions.
|
|
* @param input the user input, so far
|
|
* @param cursor the current position of the cursors in the given {@code input} text
|
|
* @param anchor outgoing parameter - when an option will be completed, the text between
|
|
* the anchor and cursor will be deleted and replaced with the given option
|
|
* @return list of candidate continuations of the given input.
|
|
*/
|
|
public abstract List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor);
|
|
|
|
/**
|
|
* Compute documentation for the given user's input. Multiple {@code Documentation} objects may
|
|
* be returned when multiple elements match the user's input (like for overloaded methods).
|
|
* @param input the snippet the user wrote so far
|
|
* @param cursor the current position of the cursors in the given {@code input} text
|
|
* @param computeJavadoc true if the javadoc for the given input should be computed in
|
|
* addition to the signature
|
|
* @return the documentations for the given user's input, if multiple elements match the input,
|
|
* multiple {@code Documentation} objects are returned.
|
|
*/
|
|
public abstract List<Documentation> documentation(String input, int cursor, boolean computeJavadoc);
|
|
|
|
/**
|
|
* Infer the type of the given expression. The expression spans from the beginning of {@code code}
|
|
* to the given {@code cursor} position. Returns null if the type of the expression cannot
|
|
* be inferred.
|
|
*
|
|
* @param code the expression for which the type should be inferred
|
|
* @param cursor current cursor position in the given code
|
|
* @return the inferred type, or null if it cannot be inferred
|
|
*/
|
|
public abstract String analyzeType(String code, int cursor);
|
|
|
|
/**
|
|
* List qualified names known for the simple name in the given code immediately
|
|
* to the left of the given cursor position. The qualified names are gathered by inspecting the
|
|
* classpath used by eval (see {@link JShell#addToClasspath(java.lang.String)}).
|
|
*
|
|
* @param code the expression for which the candidate qualified names should be computed
|
|
* @param cursor current cursor position in the given code
|
|
* @return the known qualified names
|
|
*/
|
|
public abstract QualifiedNames listQualifiedNames(String code, int cursor);
|
|
|
|
/**
|
|
* Returns the wrapper information for the {@code Snippet}. The wrapper changes as
|
|
* the environment changes, so calls to this method at different times may
|
|
* yield different results.
|
|
*
|
|
* @param snippet the {@code Snippet} from which to retrieve the wrapper
|
|
* @return information on the wrapper
|
|
*/
|
|
public abstract SnippetWrapper wrapper(Snippet snippet);
|
|
|
|
/**
|
|
* Returns the wrapper information for the snippet within the
|
|
* input source string.
|
|
* <p>
|
|
* Wrapper information for malformed and incomplete
|
|
* snippets also generate wrappers. The list is in snippet encounter
|
|
* order. The wrapper changes as the environment changes, so calls to this
|
|
* method at different times may yield different results.
|
|
* <p>
|
|
* The input should be
|
|
* exactly one complete snippet of source code, that is, one expression,
|
|
* statement, variable declaration, method declaration, class declaration,
|
|
* or import.
|
|
* To break arbitrary input into individual complete snippets, use
|
|
* {@link SourceCodeAnalysis#analyzeCompletion(String)}.
|
|
* <p>
|
|
* The wrapper may not match that returned by
|
|
* {@link SourceCodeAnalysis#wrapper(Snippet) wrapper(Snippet)},
|
|
* were the source converted to a {@code Snippet}.
|
|
*
|
|
* @param input the source input from which to generate wrappers
|
|
* @return a list of wrapper information
|
|
*/
|
|
public abstract List<SnippetWrapper> wrappers(String input);
|
|
|
|
/**
|
|
* Converts the source code of a snippet into a {@link Snippet} object (or
|
|
* list of {@code Snippet} objects in the case of some var declarations,
|
|
* e.g.: int x, y, z;).
|
|
* Does not install the snippets: declarations are not
|
|
* accessible by other snippets; imports are not added.
|
|
* Does not execute the snippets.
|
|
* <p>
|
|
* Queries may be done on the {@code Snippet} object. The {@link Snippet#id()}
|
|
* will be {@code "*UNASSOCIATED*"}.
|
|
* The returned snippets are not associated with the
|
|
* {@link JShell} instance, so attempts to pass them to {@code JShell}
|
|
* methods will throw an {@code IllegalArgumentException}, unless otherwise
|
|
* noted.
|
|
* They will not appear in queries for snippets --
|
|
* for example, {@link JShell#snippets() }.
|
|
* <p>
|
|
* Restrictions on the input are as in {@link JShell#eval(String)}.
|
|
* <p>
|
|
* Only preliminary compilation is performed, sufficient to build the
|
|
* {@code Snippet}. Snippets known to be erroneous, are returned as
|
|
* {@link ErroneousSnippet}, other snippets may or may not be in error.
|
|
*
|
|
* @param input The input String to convert
|
|
* @return usually a singleton list of Snippet, but may be empty or multiple
|
|
* @throws IllegalStateException if the {@code JShell} instance is closed.
|
|
*
|
|
* @since 10
|
|
*/
|
|
public abstract List<Snippet> sourceToSnippets(String input);
|
|
|
|
/**
|
|
* Returns a collection of {@code Snippet}s which might need updating if the
|
|
* given {@code Snippet} is updated. The returned collection is designed to
|
|
* be inclusive and may include many false positives.
|
|
*
|
|
* @param snippet the {@code Snippet} whose dependents are requested
|
|
* @return the collection of dependents
|
|
*/
|
|
public abstract Collection<Snippet> dependents(Snippet snippet);
|
|
|
|
/**
|
|
* Returns a collection of {@code Highlight}s which can be used to color
|
|
* the given snippet.
|
|
* <p>
|
|
* The returned {@code Highlight}s do not overlap, and are sorted by their
|
|
* start position.
|
|
*
|
|
* @param snippet the snippet for which the {@code Highlight}s should be computed
|
|
* @return the computed {@code Highlight}s.
|
|
* @since 19
|
|
*/
|
|
public abstract List<Highlight> highlights(String snippet);
|
|
|
|
/**
|
|
* Internal only constructor
|
|
*/
|
|
SourceCodeAnalysis() {}
|
|
|
|
/**
|
|
* The result of {@code analyzeCompletion(String input)}.
|
|
* Describes the completeness of the first snippet in the given input.
|
|
*/
|
|
public interface CompletionInfo {
|
|
|
|
/**
|
|
* The analyzed completeness of the input.
|
|
*
|
|
* @return an enum describing the completeness of the input string.
|
|
*/
|
|
Completeness completeness();
|
|
|
|
/**
|
|
* Input remaining after the complete part of the source.
|
|
*
|
|
* @return the portion of the input string that remains after the
|
|
* complete Snippet
|
|
*/
|
|
String remaining();
|
|
|
|
/**
|
|
* Source code for the first Snippet of code input. For example, first
|
|
* statement, or first method declaration. Trailing semicolons will be
|
|
* added, as needed.
|
|
*
|
|
* @return the source of the first encountered Snippet
|
|
*/
|
|
String source();
|
|
}
|
|
|
|
/**
|
|
* Describes the completeness of the given input.
|
|
*/
|
|
public enum Completeness {
|
|
/**
|
|
* The input is a complete source snippet (declaration or statement) as is.
|
|
*/
|
|
COMPLETE(true),
|
|
|
|
/**
|
|
* With this addition of a semicolon the input is a complete source snippet.
|
|
* This will only be returned when the end of input is encountered.
|
|
*/
|
|
COMPLETE_WITH_SEMI(true),
|
|
|
|
/**
|
|
* There must be further source beyond the given input in order for it
|
|
* to be complete. A semicolon would not complete it.
|
|
* This will only be returned when the end of input is encountered.
|
|
*/
|
|
DEFINITELY_INCOMPLETE(false),
|
|
|
|
/**
|
|
* A statement with a trailing (non-terminated) empty statement.
|
|
* Though technically it would be a complete statement
|
|
* with the addition of a semicolon, it is rare
|
|
* that that assumption is the desired behavior.
|
|
* The input is considered incomplete. Comments and white-space are
|
|
* still considered empty.
|
|
*/
|
|
CONSIDERED_INCOMPLETE(false),
|
|
|
|
|
|
/**
|
|
* An empty input.
|
|
* The input is considered incomplete. Comments and white-space are
|
|
* still considered empty.
|
|
*/
|
|
EMPTY(false),
|
|
|
|
/**
|
|
* The completeness of the input could not be determined because it
|
|
* contains errors. Error detection is not a goal of completeness
|
|
* analysis, however errors interfered with determining its completeness.
|
|
* The input is considered complete because evaluating is the best
|
|
* mechanism to get error information.
|
|
*/
|
|
UNKNOWN(true);
|
|
|
|
private final boolean isComplete;
|
|
|
|
Completeness(boolean isComplete) {
|
|
this.isComplete = isComplete;
|
|
}
|
|
|
|
/**
|
|
* Indicates whether the first snippet of source is complete.
|
|
* For example, "{@code x=}" is not
|
|
* complete, but "{@code x=2}" is complete, even though a subsequent line could
|
|
* make it "{@code x=2+2}". Already erroneous code is marked complete.
|
|
*
|
|
* @return {@code true} if the input is or begins a complete Snippet;
|
|
* otherwise {@code false}
|
|
*/
|
|
public boolean isComplete() {
|
|
return isComplete;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A candidate for continuation of the given user's input.
|
|
*/
|
|
public interface Suggestion {
|
|
|
|
/**
|
|
* The candidate continuation of the given user's input.
|
|
*
|
|
* @return the continuation string
|
|
*/
|
|
String continuation();
|
|
|
|
/**
|
|
* Indicates whether input continuation matches the target type and is thus
|
|
* more likely to be the desired continuation. A matching continuation is
|
|
* preferred.
|
|
*
|
|
* @return {@code true} if this suggested continuation matches the
|
|
* target type; otherwise {@code false}
|
|
*/
|
|
boolean matchesType();
|
|
}
|
|
|
|
/**
|
|
* A documentation for a candidate for continuation of the given user's input.
|
|
*/
|
|
public interface Documentation {
|
|
|
|
/**
|
|
* The signature of the given element.
|
|
*
|
|
* @return the signature
|
|
*/
|
|
String signature();
|
|
|
|
/**
|
|
* The javadoc of the given element.
|
|
*
|
|
* @return the javadoc, or null if not found or not requested
|
|
*/
|
|
String javadoc();
|
|
}
|
|
|
|
/**
|
|
* List of possible qualified names.
|
|
*/
|
|
public static final class QualifiedNames {
|
|
|
|
private final List<String> names;
|
|
private final int simpleNameLength;
|
|
private final boolean upToDate;
|
|
private final boolean resolvable;
|
|
|
|
QualifiedNames(List<String> names, int simpleNameLength, boolean upToDate, boolean resolvable) {
|
|
this.names = names;
|
|
this.simpleNameLength = simpleNameLength;
|
|
this.upToDate = upToDate;
|
|
this.resolvable = resolvable;
|
|
}
|
|
|
|
/**
|
|
* Known qualified names for the given simple name in the original code.
|
|
*
|
|
* @return known qualified names
|
|
*/
|
|
public List<String> getNames() {
|
|
return names;
|
|
}
|
|
|
|
/**
|
|
* The length of the simple name in the original code for which the
|
|
* qualified names where gathered.
|
|
*
|
|
* @return the length of the simple name; -1 if there is no name immediately left to the cursor for
|
|
* which the candidates could be computed
|
|
*/
|
|
public int getSimpleNameLength() {
|
|
return simpleNameLength;
|
|
}
|
|
|
|
/**
|
|
* Indicates whether the result is based on up-to-date data. The
|
|
* {@link SourceCodeAnalysis#listQualifiedNames(java.lang.String, int) listQualifiedNames}
|
|
* method may return before the classpath is fully inspected, in which case this method will
|
|
* return {@code false}. If the result is based on a fully inspected classpath, this method
|
|
* will return {@code true}.
|
|
*
|
|
* @return {@code true} if the result is based on up-to-date data;
|
|
* otherwise {@code false}
|
|
*/
|
|
public boolean isUpToDate() {
|
|
return upToDate;
|
|
}
|
|
|
|
/**
|
|
* Indicates whether the given simple name in the original code refers
|
|
* to a resolvable element.
|
|
*
|
|
* @return {@code true} if the given simple name in the original code
|
|
* refers to a resolvable element; otherwise {@code false}
|
|
*/
|
|
public boolean isResolvable() {
|
|
return resolvable;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* The wrapping of a snippet of Java source into valid top-level Java
|
|
* source. The wrapping will always either be an import or include a
|
|
* synthetic class at the top-level. If a synthetic class is generated, it
|
|
* will be proceeded by the package and import declarations, and may contain
|
|
* synthetic class members.
|
|
* <p>
|
|
* This interface, in addition to the mapped form, provides the context and
|
|
* position mapping information.
|
|
*/
|
|
public interface SnippetWrapper {
|
|
|
|
/**
|
|
* Returns the input that is wrapped. For
|
|
* {@link SourceCodeAnalysis#wrappers(java.lang.String) wrappers(String)},
|
|
* this is the source of the snippet within the input. A variable
|
|
* declaration of {@code N} variables will map to {@code N} wrappers
|
|
* with the source separated.
|
|
* <p>
|
|
* For {@link SourceCodeAnalysis#wrapper(Snippet) wrapper(Snippet)},
|
|
* this is {@link Snippet#source() }.
|
|
*
|
|
* @return the input source corresponding to the wrapper.
|
|
*/
|
|
String source();
|
|
|
|
/**
|
|
* Returns a Java class definition that wraps the
|
|
* {@link SnippetWrapper#source()} or, if an import, the import source.
|
|
* <p>
|
|
* If the input is not a valid Snippet, this will not be a valid
|
|
* class/import definition.
|
|
* <p>
|
|
* The source may be divided and mapped to different locations within
|
|
* the wrapped source.
|
|
*
|
|
* @return the source wrapped into top-level Java code
|
|
*/
|
|
String wrapped();
|
|
|
|
/**
|
|
* Returns the fully qualified class name of the
|
|
* {@link SnippetWrapper#wrapped() } class.
|
|
* For erroneous input, a best guess is returned.
|
|
*
|
|
* @return the name of the synthetic wrapped class; if an import, the
|
|
* name is not defined
|
|
*/
|
|
String fullClassName();
|
|
|
|
/**
|
|
* Returns the {@link Snippet.Kind} of the
|
|
* {@link SnippetWrapper#source()}.
|
|
*
|
|
* @return an enum representing the general kind of snippet.
|
|
*/
|
|
Snippet.Kind kind();
|
|
|
|
/**
|
|
* Maps character position within the source to character position
|
|
* within the wrapped.
|
|
*
|
|
* @param pos the position in {@link SnippetWrapper#source()}
|
|
* @return the corresponding position in
|
|
* {@link SnippetWrapper#wrapped() }
|
|
*/
|
|
int sourceToWrappedPosition(int pos);
|
|
|
|
/**
|
|
* Maps character position within the wrapped to character position
|
|
* within the source.
|
|
*
|
|
* @param pos the position in {@link SnippetWrapper#wrapped()}
|
|
* @return the corresponding position in
|
|
* {@link SnippetWrapper#source() }
|
|
*/
|
|
int wrappedToSourcePosition(int pos);
|
|
}
|
|
|
|
/**Assigns attributes usable for coloring to spans inside a snippet.
|
|
*
|
|
* @param start the starting position of the span
|
|
* @param end the ending position of the span
|
|
* @param attributes the attributes assigned to the span
|
|
* @since 19
|
|
*/
|
|
public record Highlight(int start, int end, Set<Attribute> attributes) {}
|
|
|
|
/**
|
|
* A span attribute which can be used to derive a coloring.
|
|
* @since 19
|
|
*/
|
|
public enum Attribute {
|
|
/**
|
|
* The span refers to a declaration of an element.
|
|
*/
|
|
DECLARATION,
|
|
/**
|
|
* The span refers to a deprecated element.
|
|
*/
|
|
DEPRECATED,
|
|
/**
|
|
* The span is a keyword.
|
|
*/
|
|
KEYWORD;
|
|
}
|
|
}
|