mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Parse new-style format substitutions parameters
Extract superclass of percent and new-style substitution chunks
This commit is contained in:
@@ -40,11 +40,11 @@ public class PySubstitutionChunkReference extends PsiReferenceBase<PyStringLiter
|
||||
private final boolean myIsPercent;
|
||||
|
||||
public PySubstitutionChunkReference(@NotNull final PyStringLiteralExpression element,
|
||||
@NotNull final PyStringFormatParser.SubstitutionChunk chunk, final int position, boolean isPercent) {
|
||||
@NotNull final PyStringFormatParser.SubstitutionChunk chunk, final int position) {
|
||||
super(element, getKeywordRange(element, chunk));
|
||||
myChunk = chunk;
|
||||
myPosition = position;
|
||||
myIsPercent = isPercent;
|
||||
myIsPercent = chunk instanceof PyStringFormatParser.PercentSubstitutionChunk;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
+5
-5
@@ -40,23 +40,23 @@ public class PythonFormattedStringReferenceProvider extends PsiReferenceProvider
|
||||
private static PySubstitutionChunkReference[] getReferencesFromFormatString(@NotNull final PyStringLiteralExpression element) {
|
||||
final List<PyStringFormatParser.SubstitutionChunk> chunks = PyStringFormatParser.filterSubstitutions(
|
||||
PyStringFormatParser.parseNewStyleFormat(element.getText()));
|
||||
return getReferencesFromChunks(element, chunks, false);
|
||||
return getReferencesFromChunks(element, chunks);
|
||||
}
|
||||
|
||||
private static PySubstitutionChunkReference[] getReferencesFromPercentString(@NotNull final PyStringLiteralExpression element) {
|
||||
final List<PyStringFormatParser.SubstitutionChunk>
|
||||
chunks = PyStringFormatParser.filterSubstitutions(PyStringFormatParser.parsePercentFormat(element.getText()));
|
||||
return getReferencesFromChunks(element, chunks, true);
|
||||
return getReferencesFromChunks(element, chunks);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static PySubstitutionChunkReference[] getReferencesFromChunks(@NotNull final PyStringLiteralExpression element,
|
||||
@NotNull final List<PyStringFormatParser.SubstitutionChunk> chunks,
|
||||
boolean isPercent) {
|
||||
@NotNull final List<PyStringFormatParser.SubstitutionChunk> chunks) {
|
||||
|
||||
final PySubstitutionChunkReference[] result = new PySubstitutionChunkReference[chunks.size()];
|
||||
for (int i = 0; i < chunks.size(); i++) {
|
||||
final PyStringFormatParser.SubstitutionChunk chunk = chunks.get(i);
|
||||
result[i] = new PySubstitutionChunkReference(element, chunk, i, isPercent);
|
||||
result[i] = new PySubstitutionChunkReference(element, chunk, i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ public class PyStringFormatInspection extends PyInspection {
|
||||
|
||||
public static class Visitor extends PyInspectionVisitor {
|
||||
private static class Inspection {
|
||||
private static final ImmutableMap<Character, String> FORMAT_CONVERSIONS = ImmutableMap.<Character, String>builder()
|
||||
private static final ImmutableMap<Character, String> PERCENT_FORMAT_CONVERSIONS = ImmutableMap.<Character, String>builder()
|
||||
.put('d', "int or long or float")
|
||||
.put('i', "int or long or float")
|
||||
.put('o', "int or long or float")
|
||||
@@ -364,7 +364,7 @@ public class PyStringFormatInspection extends PyInspection {
|
||||
}
|
||||
}
|
||||
|
||||
private void inspectFormat(@NotNull final PyStringLiteralExpression formatExpression) {
|
||||
private void inspectPercentFormat(@NotNull final PyStringLiteralExpression formatExpression) {
|
||||
final String value = formatExpression.getStringValue();
|
||||
final List<PyStringFormatParser.SubstitutionChunk> chunks = filterSubstitutions(parsePercentFormat(value));
|
||||
|
||||
@@ -374,40 +374,42 @@ public class PyStringFormatInspection extends PyInspection {
|
||||
// if use mapping keys
|
||||
final boolean mapping = chunks.size() > 0 && chunks.get(0).getMappingKey() != null;
|
||||
for (int i = 0; i < chunks.size(); ++i) {
|
||||
PyStringFormatParser.SubstitutionChunk chunk = chunks.get(i);
|
||||
|
||||
// Mapping key
|
||||
String mappingKey = Integer.toString(i + 1);
|
||||
if (mapping) {
|
||||
if (chunk.getMappingKey() == null || chunk.isUnclosedMapping()) {
|
||||
registerProblem(formatExpression, PyBundle.message("INSP.too.few.keys"));
|
||||
break;
|
||||
|
||||
PyStringFormatParser.PercentSubstitutionChunk chunk = as(chunks.get(i), PyStringFormatParser.PercentSubstitutionChunk.class);
|
||||
if (chunk != null) {
|
||||
// Mapping key
|
||||
String mappingKey = Integer.toString(i + 1);
|
||||
if (mapping) {
|
||||
if (chunk.getMappingKey() == null || chunk.isUnclosedMapping()) {
|
||||
registerProblem(formatExpression, PyBundle.message("INSP.too.few.keys"));
|
||||
break;
|
||||
}
|
||||
mappingKey = chunk.getMappingKey();
|
||||
myUsedMappingKeys.put(mappingKey, false);
|
||||
}
|
||||
mappingKey = chunk.getMappingKey();
|
||||
myUsedMappingKeys.put(mappingKey, false);
|
||||
}
|
||||
|
||||
// Minimum field width
|
||||
inspectWidth(formatExpression, chunk.getWidth());
|
||||
// Minimum field width
|
||||
inspectWidth(formatExpression, chunk.getWidth());
|
||||
|
||||
// Precision
|
||||
inspectWidth(formatExpression, chunk.getPrecision());
|
||||
// Precision
|
||||
inspectWidth(formatExpression, chunk.getPrecision());
|
||||
|
||||
// Format specifier
|
||||
final char conversionType = chunk.getConversionType();
|
||||
if (conversionType == 'b') {
|
||||
final LanguageLevel languageLevel = LanguageLevel.forElement(formatExpression);
|
||||
if (languageLevel.isOlderThan(LanguageLevel.PYTHON35) || !isBytesLiteral(formatExpression, myTypeEvalContext)) {
|
||||
registerProblem(formatExpression, "Unsupported format character 'b'");
|
||||
return;
|
||||
// Format specifier
|
||||
final char conversionType = chunk.getConversionType();
|
||||
if (conversionType == 'b') {
|
||||
final LanguageLevel languageLevel = LanguageLevel.forElement(formatExpression);
|
||||
if (languageLevel.isOlderThan(LanguageLevel.PYTHON35) || !isBytesLiteral(formatExpression, myTypeEvalContext)) {
|
||||
registerProblem(formatExpression, "Unsupported format character 'b'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (PERCENT_FORMAT_CONVERSIONS.containsKey(conversionType)) {
|
||||
myFormatSpec.put(mappingKey, PERCENT_FORMAT_CONVERSIONS.get(conversionType));
|
||||
continue;
|
||||
}
|
||||
registerProblem(formatExpression, PyBundle.message("INSP.no.format.specifier.char"), new PyAddSpecifierToFormatQuickFix());
|
||||
return;
|
||||
}
|
||||
if (FORMAT_CONVERSIONS.containsKey(conversionType)) {
|
||||
myFormatSpec.put(mappingKey, FORMAT_CONVERSIONS.get(conversionType));
|
||||
continue;
|
||||
}
|
||||
registerProblem(formatExpression, PyBundle.message("INSP.no.format.specifier.char"), new PyAddSpecifierToFormatQuickFix());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,7 +474,7 @@ public class PyStringFormatInspection extends PyInspection {
|
||||
if (node.getLeftExpression() instanceof PyStringLiteralExpression && node.isOperator("%")) {
|
||||
final Inspection inspection = new Inspection(this, myTypeEvalContext);
|
||||
final PyStringLiteralExpression literalExpression = (PyStringLiteralExpression)node.getLeftExpression();
|
||||
inspection.inspectFormat(literalExpression);
|
||||
inspection.inspectPercentFormat(literalExpression);
|
||||
if (inspection.isProblem()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -69,83 +69,19 @@ public class PyStringFormatParser {
|
||||
|
||||
public static class SubstitutionChunk extends FormatStringChunk {
|
||||
@Nullable private String myMappingKey;
|
||||
@Nullable private String myConversionFlags;
|
||||
@Nullable private String myWidth;
|
||||
@Nullable private String myPrecision;
|
||||
@Nullable private Integer myPosition;
|
||||
@Nullable private Integer myAutoPosition;
|
||||
private char myLengthModifier;
|
||||
private char myConversionType;
|
||||
private boolean myUnclosedMapping;
|
||||
|
||||
public SubstitutionChunk(int startIndex) {
|
||||
super(startIndex, startIndex);
|
||||
public SubstitutionChunk(int startIndex, int endIndex) {
|
||||
super(startIndex, endIndex);
|
||||
}
|
||||
|
||||
protected void setEndIndex(int endIndex) {
|
||||
myEndIndex = endIndex;
|
||||
}
|
||||
|
||||
public char getConversionType() {
|
||||
return myConversionType;
|
||||
}
|
||||
|
||||
private void setConversionType(char conversionType) {
|
||||
myConversionType = conversionType;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getMappingKey() {
|
||||
return myMappingKey;
|
||||
}
|
||||
|
||||
protected void setMappingKey(@Nullable String mappingKey) {
|
||||
myMappingKey = mappingKey;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getConversionFlags() {
|
||||
return myConversionFlags;
|
||||
}
|
||||
|
||||
private void setConversionFlags(@Nullable String conversionFlags) {
|
||||
myConversionFlags = conversionFlags;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getWidth() {
|
||||
return myWidth;
|
||||
}
|
||||
|
||||
private void setWidth(@Nullable String width) {
|
||||
myWidth = width;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getPrecision() {
|
||||
return myPrecision;
|
||||
}
|
||||
|
||||
private void setPrecision(@Nullable String precision) {
|
||||
myPrecision = precision;
|
||||
}
|
||||
|
||||
public char getLengthModifier() {
|
||||
return myLengthModifier;
|
||||
}
|
||||
|
||||
private void setLengthModifier(char lengthModifier) {
|
||||
myLengthModifier = lengthModifier;
|
||||
}
|
||||
|
||||
public boolean isUnclosedMapping() {
|
||||
return myUnclosedMapping;
|
||||
}
|
||||
|
||||
private void setUnclosedMapping(boolean unclosedMapping) {
|
||||
myUnclosedMapping = unclosedMapping;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getPosition() {
|
||||
return myPosition;
|
||||
@@ -171,6 +107,169 @@ public class PyStringFormatParser {
|
||||
protected void setAutoPosition(@Nullable Integer autoPosition) {
|
||||
myAutoPosition = autoPosition;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getPrecision() {
|
||||
return myPrecision;
|
||||
}
|
||||
|
||||
public void setPrecision(@Nullable String precision) {
|
||||
myPrecision = precision;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getWidth() {
|
||||
return myWidth;
|
||||
}
|
||||
|
||||
public void setWidth(@Nullable String width) {
|
||||
myWidth = width;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getMappingKey() {
|
||||
return myMappingKey;
|
||||
}
|
||||
|
||||
protected void setMappingKey(@Nullable String mappingKey) {
|
||||
myMappingKey = mappingKey;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PercentSubstitutionChunk extends SubstitutionChunk {
|
||||
@Nullable private String myConversionFlags;
|
||||
private char myLengthModifier;
|
||||
private char myConversionType;
|
||||
private boolean myUnclosedMapping;
|
||||
|
||||
public PercentSubstitutionChunk(int startIndex) {
|
||||
super(startIndex, startIndex);
|
||||
}
|
||||
|
||||
public char getConversionType() {
|
||||
return myConversionType;
|
||||
}
|
||||
|
||||
private void setConversionType(char conversionType) {
|
||||
myConversionType = conversionType;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getConversionFlags() {
|
||||
return myConversionFlags;
|
||||
}
|
||||
|
||||
private void setConversionFlags(@Nullable String conversionFlags) {
|
||||
myConversionFlags = conversionFlags;
|
||||
}
|
||||
|
||||
public char getLengthModifier() {
|
||||
return myLengthModifier;
|
||||
}
|
||||
|
||||
private void setLengthModifier(char lengthModifier) {
|
||||
myLengthModifier = lengthModifier;
|
||||
}
|
||||
|
||||
public boolean isUnclosedMapping() {
|
||||
return myUnclosedMapping;
|
||||
}
|
||||
|
||||
private void setUnclosedMapping(boolean unclosedMapping) {
|
||||
myUnclosedMapping = unclosedMapping;
|
||||
}
|
||||
}
|
||||
public static class NewStyleSubstitutionChunk extends SubstitutionChunk {
|
||||
@Nullable private String myMappingKey;
|
||||
@Nullable private String myConversion;
|
||||
@Nullable private String myWidth;
|
||||
@Nullable private String myPrecision;
|
||||
@Nullable private Integer myPosition;
|
||||
private char myConversionType;
|
||||
private boolean signOption;
|
||||
private boolean zeroPadding;
|
||||
private boolean alternateForm;
|
||||
private boolean thousandsSeparator;
|
||||
|
||||
public NewStyleSubstitutionChunk(int startIndex) {
|
||||
super(startIndex, startIndex);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getMappingKey() {
|
||||
return myMappingKey;
|
||||
}
|
||||
|
||||
public void setMappingKey(@Nullable String mappingKey) {
|
||||
myMappingKey = mappingKey;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getConversion() {
|
||||
return myConversion;
|
||||
}
|
||||
|
||||
public void setConversion(@Nullable String conversion) {
|
||||
myConversion = conversion;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getWidth() {
|
||||
return myWidth;
|
||||
}
|
||||
|
||||
public void setWidth(@Nullable String width) {
|
||||
myWidth = width;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getPrecision() {
|
||||
return myPrecision;
|
||||
}
|
||||
|
||||
public void setPrecision(@Nullable String precision) {
|
||||
myPrecision = precision;
|
||||
}
|
||||
|
||||
public boolean hasSignOption() {
|
||||
return signOption;
|
||||
}
|
||||
|
||||
public void setSignOption(boolean signOption) {
|
||||
this.signOption = signOption;
|
||||
}
|
||||
|
||||
public char getConversionType() {
|
||||
return myConversionType;
|
||||
}
|
||||
|
||||
public void setConversionType(char conversionType) {
|
||||
myConversionType = conversionType;
|
||||
}
|
||||
|
||||
public boolean useAlternateForm() {
|
||||
return alternateForm;
|
||||
}
|
||||
|
||||
public void setAlternateForm(boolean alternateForm) {
|
||||
this.alternateForm = alternateForm;
|
||||
}
|
||||
|
||||
public boolean hasZeroPadding() {
|
||||
return zeroPadding;
|
||||
}
|
||||
|
||||
public void setZeroPadding(boolean zeroPadding) {
|
||||
this.zeroPadding = zeroPadding;
|
||||
}
|
||||
|
||||
public boolean hasThousandsSeparator() {
|
||||
return thousandsSeparator;
|
||||
}
|
||||
|
||||
public void setThousandsSeparator(boolean thousandsSeparator) {
|
||||
this.thousandsSeparator = thousandsSeparator;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull private final String myLiteral;
|
||||
@@ -178,11 +277,21 @@ public class PyStringFormatParser {
|
||||
private int myPos;
|
||||
private int mySubstitutionsCount = 0;
|
||||
|
||||
// % strings
|
||||
private static final String CONVERSION_FLAGS = "#0- +";
|
||||
private static final String DIGITS = "0123456789";
|
||||
private static final String LENGTH_MODIFIERS = "hlL";
|
||||
private static final String VALID_CONVERSION_TYPES = "diouxXeEfFgGcrsb";
|
||||
|
||||
// new style strings
|
||||
private static final String ALIGN_SYMBOLS = "<>=^";
|
||||
private static final String SIGN_SYMBOLS = "+- ";
|
||||
private static final String NEW_STYLE_CONVERSION_TYPES = "bcdeEfFgGnosxX%";
|
||||
private static final String CONVERSIONS = "rsa";
|
||||
private static final char ALTERNATE_FORM_SYMBOL = '#';
|
||||
private static final char ZERO_PADDING_SYMBOL = '0';
|
||||
|
||||
|
||||
private PyStringFormatParser(@NotNull String literal) {
|
||||
myLiteral = literal;
|
||||
}
|
||||
@@ -194,42 +303,7 @@ public class PyStringFormatParser {
|
||||
|
||||
@NotNull
|
||||
public static List<FormatStringChunk> parseNewStyleFormat(@NotNull String s) {
|
||||
final List<FormatStringChunk> results = new ArrayList<>();
|
||||
final Matcher matcher = NEW_STYLE_FORMAT_TOKENS.matcher(s);
|
||||
int autoPositionedFieldsCount = 0;
|
||||
while (matcher.find()) {
|
||||
final String group = matcher.group();
|
||||
final int start = matcher.start();
|
||||
final int end = matcher.end();
|
||||
if ("{{".equals(group) || "}}".equals(group)) {
|
||||
results.add(new ConstantChunk(start, end));
|
||||
}
|
||||
else if (group.startsWith("{") && group.endsWith("}")) {
|
||||
final SubstitutionChunk chunk = new SubstitutionChunk(start);
|
||||
chunk.setEndIndex(end);
|
||||
final int nameStart = start + 1;
|
||||
final int nameEnd = StringUtil.indexOfAny(s, "!:.[}", nameStart, end);
|
||||
if (nameEnd > 0 && nameStart < nameEnd) {
|
||||
final String name = s.substring(nameStart, nameEnd);
|
||||
try {
|
||||
final int number = Integer.parseInt(name);
|
||||
chunk.setPosition(number);
|
||||
} catch (NumberFormatException e) {
|
||||
chunk.setMappingKey(name);
|
||||
}
|
||||
}
|
||||
else {
|
||||
chunk.setAutoPosition(autoPositionedFieldsCount);
|
||||
autoPositionedFieldsCount++;
|
||||
}
|
||||
// TODO: Parse substitution details
|
||||
results.add(chunk);
|
||||
}
|
||||
else {
|
||||
results.add(new ConstantChunk(start, end));
|
||||
}
|
||||
}
|
||||
return results;
|
||||
return new PyStringFormatParser(s).parseNewStyle();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -253,9 +327,107 @@ public class PyStringFormatParser {
|
||||
return myResult;
|
||||
}
|
||||
|
||||
private List<FormatStringChunk> parseNewStyle() {
|
||||
final List<FormatStringChunk> results = new ArrayList<FormatStringChunk>();
|
||||
final Matcher matcher = NEW_STYLE_FORMAT_TOKENS.matcher(myLiteral);
|
||||
int autoPositionedFieldsCount = 0;
|
||||
while (matcher.find()) {
|
||||
final String group = matcher.group();
|
||||
myPos = matcher.start();
|
||||
final int end = matcher.end();
|
||||
if ("{{".equals(group) || "}}".equals(group)) {
|
||||
results.add(new ConstantChunk(myPos, end));
|
||||
}
|
||||
else if (group.startsWith("{") && group.endsWith("}")) {
|
||||
autoPositionedFieldsCount = parseNewStyleSubstitution(results, end, autoPositionedFieldsCount);
|
||||
}
|
||||
else {
|
||||
results.add(new ConstantChunk(myPos, end));
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private int parseNewStyleSubstitution(List<FormatStringChunk> results, int end, int autoPositionedFieldsCount) {
|
||||
final NewStyleSubstitutionChunk chunk = new NewStyleSubstitutionChunk(myPos);
|
||||
chunk.setEndIndex(end);
|
||||
|
||||
// skip "{"
|
||||
myPos++;
|
||||
// name
|
||||
final int nameEnd = StringUtil.indexOfAny(myLiteral, "!:.[}", myPos, end);
|
||||
if (nameEnd > 0 && myPos < nameEnd) {
|
||||
final String name = myLiteral.substring(myPos, nameEnd);
|
||||
try {
|
||||
final int number = Integer.parseInt(name);
|
||||
chunk.setPosition(number);
|
||||
} catch (NumberFormatException e) {
|
||||
chunk.setMappingKey(name);
|
||||
}
|
||||
}
|
||||
else {
|
||||
chunk.setAutoPosition(autoPositionedFieldsCount);
|
||||
autoPositionedFieldsCount++;
|
||||
}
|
||||
|
||||
// conversion
|
||||
myPos = Math.max(myPos, StringUtil.indexOf(myLiteral, '!', myPos, end) + 1);
|
||||
final int conversionEnd = StringUtil.indexOfAny(myLiteral, ":}", myPos, end);
|
||||
if (conversionEnd - myPos == 1) {
|
||||
final String conversion = myLiteral.substring(myPos, conversionEnd);
|
||||
if (StringUtil.containsAnyChar(conversion, CONVERSIONS)) {
|
||||
chunk.setConversion(conversion);
|
||||
myPos = conversionEnd + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// parse format spec
|
||||
// [[fill]align][sign][#][0][width][,][.precision][type]
|
||||
if (isAt(':')) {
|
||||
myPos++;
|
||||
|
||||
//skip align options
|
||||
myPos = Math.max(myPos, StringUtil.indexOfAny(myLiteral, ALIGN_SYMBOLS, myPos, end) + 1);
|
||||
|
||||
if (isAtSet(SIGN_SYMBOLS)) {
|
||||
chunk.setSignOption(true);
|
||||
myPos++;
|
||||
}
|
||||
|
||||
if (isAt(ALTERNATE_FORM_SYMBOL)) {
|
||||
chunk.setAlternateForm(true);
|
||||
myPos++;
|
||||
}
|
||||
|
||||
if (isAt(ZERO_PADDING_SYMBOL)) {
|
||||
chunk.setZeroPadding(true);
|
||||
myPos++;
|
||||
}
|
||||
|
||||
chunk.setWidth(parseWhileCharacterInSet(DIGITS));
|
||||
|
||||
if (isAt(',')) {
|
||||
myPos++;
|
||||
chunk.setThousandsSeparator(true);
|
||||
}
|
||||
|
||||
if (isAt('.')) {
|
||||
myPos++;
|
||||
chunk.setPrecision(parseWhileCharacterInSet(DIGITS));
|
||||
}
|
||||
|
||||
if (isAtSet(NEW_STYLE_CONVERSION_TYPES)) {
|
||||
chunk.setConversionType(myLiteral.charAt(myPos));
|
||||
}
|
||||
}
|
||||
|
||||
results.add(chunk);
|
||||
return autoPositionedFieldsCount;
|
||||
}
|
||||
|
||||
private void parseSubstitution() {
|
||||
assert myLiteral.charAt(myPos) == '%';
|
||||
SubstitutionChunk chunk = new SubstitutionChunk(myPos);
|
||||
PercentSubstitutionChunk chunk = new PercentSubstitutionChunk(myPos);
|
||||
myResult.add(chunk);
|
||||
myPos++;
|
||||
if (isAt('(')) {
|
||||
|
||||
+16
-14
@@ -79,20 +79,22 @@ public class PyAddSpecifierToFormatQuickFix implements LocalQuickFix {
|
||||
|
||||
int shift = 2;
|
||||
for (int i = 0; i < chunks.size(); i++) {
|
||||
final PyStringFormatParser.SubstitutionChunk chunk = chunks.get(i);
|
||||
if (elements.length <= i) return;
|
||||
final PyType type = context.getType(elements[i]);
|
||||
final char conversionType = chunk.getConversionType();
|
||||
if (conversionType == '\u0000') {
|
||||
final int insertOffset = offset + chunk.getStartIndex() + shift;
|
||||
if (insertOffset > leftExpression.getTextRange().getEndOffset()) return;
|
||||
if (PyTypeChecker.match(strType, type, context)) {
|
||||
document.insertString(insertOffset, "s");
|
||||
shift += 1;
|
||||
}
|
||||
if (PyTypeChecker.match(intType, type, context) || PyTypeChecker.match(floatType, type, context)) {
|
||||
document.insertString(insertOffset, "d");
|
||||
shift += 1;
|
||||
final PyStringFormatParser.PercentSubstitutionChunk chunk = PyUtil.as(chunks.get(i), PyStringFormatParser.PercentSubstitutionChunk.class);
|
||||
if (chunk != null) {
|
||||
if (elements.length <= i) return;
|
||||
final PyType type = context.getType(elements[i]);
|
||||
final char conversionType = chunk.getConversionType();
|
||||
if (conversionType == '\u0000') {
|
||||
final int insertOffset = offset + chunk.getStartIndex() + shift;
|
||||
if (insertOffset > leftExpression.getTextRange().getEndOffset()) return;
|
||||
if (PyTypeChecker.match(strType, type, context)) {
|
||||
document.insertString(insertOffset, "s");
|
||||
shift += 1;
|
||||
}
|
||||
if (PyTypeChecker.match(intType, type, context) || PyTypeChecker.match(floatType, type, context)) {
|
||||
document.insertString(insertOffset, "d");
|
||||
shift += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ public class PyStringFormatParserTest extends TestCase {
|
||||
public void testFormat() {
|
||||
List<FormatStringChunk> chunks = parsePercentFormat("%s");
|
||||
assertEquals(1, chunks.size());
|
||||
SubstitutionChunk chunk = (SubstitutionChunk)chunks.get(0);
|
||||
PercentSubstitutionChunk chunk = (PercentSubstitutionChunk)chunks.get(0);
|
||||
assertEquals(0, chunk.getStartIndex());
|
||||
assertEquals(2, chunk.getEndIndex());
|
||||
assertEquals('s', chunk.getConversionType());
|
||||
@@ -68,7 +68,7 @@ public class PyStringFormatParserTest extends TestCase {
|
||||
public void testMappingKey() {
|
||||
List<FormatStringChunk> chunks = parsePercentFormat("%(language)s");
|
||||
assertEquals(1, chunks.size());
|
||||
SubstitutionChunk chunk = (SubstitutionChunk)chunks.get(0);
|
||||
PercentSubstitutionChunk chunk = (PercentSubstitutionChunk)chunks.get(0);
|
||||
assertEquals("language", chunk.getMappingKey());
|
||||
assertEquals('s', chunk.getConversionType());
|
||||
}
|
||||
@@ -76,7 +76,7 @@ public class PyStringFormatParserTest extends TestCase {
|
||||
public void testConversionFlags() {
|
||||
List<FormatStringChunk> chunks = parsePercentFormat("%#0d");
|
||||
assertEquals(1, chunks.size());
|
||||
SubstitutionChunk chunk = (SubstitutionChunk)chunks.get(0);
|
||||
PercentSubstitutionChunk chunk = (PercentSubstitutionChunk)chunks.get(0);
|
||||
assertEquals("#0", chunk.getConversionFlags());
|
||||
}
|
||||
|
||||
@@ -97,21 +97,21 @@ public class PyStringFormatParserTest extends TestCase {
|
||||
public void testLengthModifier() {
|
||||
List<FormatStringChunk> chunks = parsePercentFormat("%ld");
|
||||
assertEquals(1, chunks.size());
|
||||
SubstitutionChunk chunk = (SubstitutionChunk)chunks.get(0);
|
||||
PercentSubstitutionChunk chunk = (PercentSubstitutionChunk)chunks.get(0);
|
||||
assertEquals('l', chunk.getLengthModifier());
|
||||
}
|
||||
|
||||
public void testDoubleAsterisk() {
|
||||
List<FormatStringChunk> chunks = parsePercentFormat("%**d");
|
||||
assertEquals(2, chunks.size());
|
||||
SubstitutionChunk chunk = (SubstitutionChunk)chunks.get(0);
|
||||
PercentSubstitutionChunk chunk = (PercentSubstitutionChunk)chunks.get(0);
|
||||
assertEquals(2, chunk.getEndIndex());
|
||||
assertEquals('\0', chunk.getConversionType());
|
||||
}
|
||||
|
||||
public void testUnclosedMapping() {
|
||||
List<FormatStringChunk> chunks = parsePercentFormat("%(name1s");
|
||||
SubstitutionChunk chunk = (SubstitutionChunk)chunks.get(0);
|
||||
PercentSubstitutionChunk chunk = (PercentSubstitutionChunk)chunks.get(0);
|
||||
assertEquals("name1s", chunk.getMappingKey());
|
||||
assertTrue(chunk.isUnclosedMapping());
|
||||
}
|
||||
@@ -174,6 +174,70 @@ public class PyStringFormatParserTest extends TestCase {
|
||||
assertEquals(TextRange.create(7, 9), chunks.get(2).getTextRange());
|
||||
}
|
||||
|
||||
public void testNewStyleSign() {
|
||||
final List<FormatStringChunk> chunks = parseNewStyleFormat("{:+}");
|
||||
assertEquals(1, chunks.size());
|
||||
assertEquals(TextRange.create(0, 4), chunks.get(0).getTextRange());
|
||||
assertTrue(((NewStyleSubstitutionChunk)chunks.get(0)).hasSignOption());
|
||||
}
|
||||
|
||||
public void testNewStyleAlternateForm() {
|
||||
final List<FormatStringChunk> chunks = parseNewStyleFormat("{:#}");
|
||||
assertEquals(1, chunks.size());
|
||||
assertEquals(TextRange.create(0, 4), chunks.get(0).getTextRange());
|
||||
assertTrue(((NewStyleSubstitutionChunk)chunks.get(0)).useAlternateForm());
|
||||
}
|
||||
|
||||
public void testNewStyleZeroPadded() {
|
||||
final List<FormatStringChunk> chunks = parseNewStyleFormat("{:0}");
|
||||
assertEquals(1, chunks.size());
|
||||
assertEquals(TextRange.create(0, 4), chunks.get(0).getTextRange());
|
||||
assertTrue(((NewStyleSubstitutionChunk)chunks.get(0)).hasZeroPadding());
|
||||
}
|
||||
|
||||
public void testNewStyleWidth() {
|
||||
final List<FormatStringChunk> chunks = parseNewStyleFormat("{:10}");
|
||||
assertEquals(1, chunks.size());
|
||||
assertEquals(TextRange.create(0, 5), chunks.get(0).getTextRange());
|
||||
assertEquals("10", ((NewStyleSubstitutionChunk)chunks.get(0)).getWidth());
|
||||
}
|
||||
|
||||
public void testNewStyleZeroPaddingWidth() {
|
||||
final List<FormatStringChunk> chunks = parseNewStyleFormat("{:010}");
|
||||
assertEquals(1, chunks.size());
|
||||
assertEquals(TextRange.create(0, 6), chunks.get(0).getTextRange());
|
||||
assertTrue(((NewStyleSubstitutionChunk)chunks.get(0)).hasZeroPadding());
|
||||
assertEquals("10", ((NewStyleSubstitutionChunk)chunks.get(0)).getWidth());
|
||||
}
|
||||
|
||||
public void testNewStyleThousandSeparator() {
|
||||
final List<FormatStringChunk> chunks = parseNewStyleFormat("{:,}");
|
||||
assertEquals(1, chunks.size());
|
||||
assertEquals(TextRange.create(0, 4), chunks.get(0).getTextRange());
|
||||
assertTrue(((NewStyleSubstitutionChunk)chunks.get(0)).hasThousandsSeparator());
|
||||
}
|
||||
|
||||
public void testNewStylePrecision() {
|
||||
final List<FormatStringChunk> chunks = parseNewStyleFormat("{:.2}");
|
||||
assertEquals(1, chunks.size());
|
||||
assertEquals(TextRange.create(0, 5), chunks.get(0).getTextRange());
|
||||
assertEquals("2", ((NewStyleSubstitutionChunk)chunks.get(0)).getPrecision());
|
||||
}
|
||||
|
||||
public void testNewStyleConversionType() {
|
||||
final List<FormatStringChunk> chunks = parseNewStyleFormat("{:d}");
|
||||
assertEquals(1, chunks.size());
|
||||
assertEquals(TextRange.create(0, 4), chunks.get(0).getTextRange());
|
||||
assertEquals('d', ((NewStyleSubstitutionChunk)chunks.get(0)).getConversionType());
|
||||
}
|
||||
|
||||
public void testNewStyleConversion() {
|
||||
final List<FormatStringChunk> chunks = parseNewStyleFormat("{!s}");
|
||||
assertEquals(1, chunks.size());
|
||||
assertEquals(TextRange.create(0, 4), chunks.get(0).getTextRange());
|
||||
assertEquals("s", ((NewStyleSubstitutionChunk)chunks.get(0)).getConversion());
|
||||
}
|
||||
|
||||
public void testNewStyleNestedFields() {
|
||||
final Field field = doParseAndGetFirstField("u'{foo:{bar} {baz}}'");
|
||||
assertEquals("foo", field.getFirstName());
|
||||
|
||||
Reference in New Issue
Block a user