blob: 933bd47ecdab7ee22c53e5a3f22e8d1469136503 [file] [log] [blame]
/*
* Copyright 2016 The Bazel Authors. All rights reserved.
*
* 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 com.google.idea.blaze.base.lang.buildfile.psi;
import com.google.idea.blaze.base.lang.buildfile.psi.Argument.Keyword;
import com.google.idea.blaze.base.lang.buildfile.psi.util.PsiUtils;
import com.google.idea.blaze.base.lang.buildfile.references.AttributeSpecificStringLiteralReferenceProvider;
import com.google.idea.blaze.base.lang.buildfile.references.ExternalWorkspaceReferenceFragment;
import com.google.idea.blaze.base.lang.buildfile.references.LabelReference;
import com.google.idea.blaze.base.lang.buildfile.references.LoadedSymbolReference;
import com.google.idea.blaze.base.lang.buildfile.references.PackageReferenceFragment;
import com.google.idea.blaze.base.lang.buildfile.references.QuoteType;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import javax.annotation.Nullable;
/** PSI node for string literal expressions */
public class StringLiteral extends BuildElementImpl implements LiteralExpression {
/**
* Removes the leading and trailing quotes. Naive implementation intended for resolving references
* (in which case escaped characters, raw strings, etc. are unlikely).
*/
public static String stripQuotes(String string) {
return textRangeInElement(string).substring(string);
}
/** The range of text characters, excluding leading and trailing quotes. */
public static TextRange textRangeInElement(String string) {
// TODO: Handle escaped characters, etc. here?
// (extract logic from BuildLexerBase.addStringLiteral)
if (string.startsWith("\"\"\"")) {
return string.length() <= 3
? TextRange.EMPTY_RANGE
: TextRange.create(3, endTrimIndex(string, '"', 3));
}
if (string.startsWith("'''")) {
return string.length() <= 3
? TextRange.EMPTY_RANGE
: TextRange.create(3, endTrimIndex(string, '\'', 3));
}
if (string.startsWith("\"")) {
return TextRange.create(1, endTrimIndex(string, '"', 1));
}
if (string.startsWith("'")) {
return TextRange.create(1, endTrimIndex(string, '\'', 1));
}
return TextRange.allOf(string);
}
private static int endTrimIndex(String string, char quoteChar, int numberQuoteChars) {
int trimIndex = string.length();
for (int i = 1;
i <= Math.min(numberQuoteChars, string.length() - numberQuoteChars);
i++, trimIndex--) {
if (string.charAt(string.length() - i) != quoteChar) {
break;
}
}
return trimIndex;
}
public static QuoteType getQuoteType(@Nullable String rawText) {
if (rawText == null) {
return QuoteType.NoQuotes;
}
if (rawText.startsWith("\"\"\"")) {
return QuoteType.TripleDouble;
}
if (rawText.startsWith("'''")) {
return QuoteType.TripleSingle;
}
if (rawText.startsWith("'")) {
return QuoteType.Single;
}
if (rawText.startsWith("\"")) {
return QuoteType.Double;
}
return QuoteType.NoQuotes;
}
public StringLiteral(ASTNode astNode) {
super(astNode);
}
@Override
protected void acceptVisitor(BuildElementVisitor visitor) {
visitor.visitStringLiteral(this);
}
/** Removes the leading and trailing quotes */
public String getStringContents() {
return stripQuotes(getText());
}
public QuoteType getQuoteType() {
return getQuoteType(getText());
}
/**
* Labels are taken to reference: - the actual target they reference - the BUILD package specified
* before the colon (only if explicitly present)
*/
@Override
public PsiReference[] getReferences() {
// first look for attribute-specific references
String attributeName = getParentAttributeName();
if (attributeName != null) {
PsiReference[] refs =
AttributeSpecificStringLiteralReferenceProvider.findReferences(attributeName, this);
if (refs.length != 0) {
return refs;
}
}
PsiReference primaryReference = getReference();
if (primaryReference instanceof LabelReference) {
LabelReference labelReference = (LabelReference) primaryReference;
return new PsiReference[] {
primaryReference,
new PackageReferenceFragment(labelReference),
new ExternalWorkspaceReferenceFragment(labelReference)
};
}
return primaryReference != null
? new PsiReference[] {primaryReference}
: PsiReference.EMPTY_ARRAY;
}
/** The primary reference -- this is the target referenced by the full label */
@Nullable
@Override
public PsiReference getReference() {
LoadStatement load = getLoadStatementParent();
if (load != null) {
StringLiteral importNode = load.getImportPsiElement();
if (importNode == null) {
return null;
}
LabelReference importReference = new LabelReference(importNode, false);
if (this.equals(importNode)) {
return importReference;
}
return new LoadedSymbolReference(this, importReference);
}
return new LabelReference(this, true);
}
/** If this string is an attribute value within a BUILD rule, return the attribute type. */
@Nullable
private String getParentAttributeName() {
Keyword parentKeyword = PsiUtils.getParentOfType(this, Keyword.class, true);
return parentKeyword != null ? parentKeyword.getName() : null;
}
@Nullable
public LoadStatement getLoadStatementParent() {
PsiElement parent = getParent();
if (parent instanceof LoadStatement) {
// the skylark extension label
return (LoadStatement) parent;
}
if (parent instanceof AssignmentStatement) {
// could be part of an aliased symbol
parent = parent.getParent();
}
return parent instanceof LoadedSymbol ? (LoadStatement) parent.getParent() : null;
}
@Override
public String getPresentableText() {
return getText();
}
}