Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,8 @@ private static Map<String, Toml.Table> indexTables(Toml.Document doc) {
for (TomlValue value : doc.getValues()) {
if (value instanceof Toml.Table) {
Toml.Table table = (Toml.Table) value;
Toml.Identifier nameId = table.getName();
if (nameId != null) {
tables.put(nameId.getName(), table);
if (table.getName() != null) {
tables.put(table.getName().getName(), table);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,21 @@ public Toml visitIdentifier(Toml.Identifier identifier, Toml other) {
return null;
}

@Override
public Toml visitDottedKey(Toml.DottedKey dottedKey, Toml other) {
if (dottedKey == other) {
return null;
}
if (!(other instanceof Toml.DottedKey)) {
areEqual = false;
return null;
}
if (!dottedKey.getPath().equals(((Toml.DottedKey) other).getPath())) {
areEqual = false;
}
return null;
}

@Override
public Toml visitEmpty(Toml.Empty empty, Toml other) {
if (empty == other) {
Expand Down Expand Up @@ -214,18 +229,9 @@ private boolean keyEquals(@Nullable TomlKey key1, @Nullable TomlKey key2) {
if (key1 == null || key2 == null) {
return false;
}

// Both keys must be of the same type
if (key1.getClass() != key2.getClass()) {
return false;
}

// Compare identifier keys
if (key1 instanceof Toml.Identifier && key2 instanceof Toml.Identifier) {
return ((Toml.Identifier) key1).getName().equals(((Toml.Identifier) key2).getName());
}

return false;
// Same canonical path counts as equal regardless of whether the key
// was authored as a simple key or a dotted key.
return key1.getPath().equals(key2.getPath());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ public Toml.Identifier visitIdentifier(Toml.Identifier identifier, P p) {
return (Toml.Identifier) super.visitIdentifier(identifier, p);
}

@Override
public Toml.DottedKey visitDottedKey(Toml.DottedKey dottedKey, P p) {
return (Toml.DottedKey) super.visitDottedKey(dottedKey, p);
}

@Override
public Toml.KeyValue visitKeyValue(Toml.KeyValue keyValue, P p) {
return (Toml.KeyValue) super.visitKeyValue(keyValue, p);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import org.jspecify.annotations.Nullable;
import org.openrewrite.Cursor;
import org.openrewrite.toml.tree.Toml;
import org.openrewrite.toml.tree.TomlKey;

import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -95,29 +94,20 @@ private List<String> buildPath(Cursor cursor) {

if (value instanceof Toml.KeyValue) {
Toml.KeyValue kv = (Toml.KeyValue) value;
TomlKey key = kv.getKey();
if (key instanceof Toml.Identifier) {
String keyName = ((Toml.Identifier) key).getName();
Cursor parent = current.getParent();
while (parent != null) {
Object parentValue = parent.getValue();
if (parentValue instanceof Toml.Table) {
Toml.Table table = (Toml.Table) parentValue;
if (table.getName() != null) {
String tableName = table.getName().getName();
// Split dotted names: [tool.poetry]
String[] parts = tableName.split("\\.");
for (int i = parts.length - 1; i >= 0; i--) {
path.add(0, parts[i].trim());
}
}
break;
Cursor parent = current.getParent();
while (parent != null) {
Object parentValue = parent.getValue();
if (parentValue instanceof Toml.Table) {
Toml.Table table = (Toml.Table) parentValue;
if (table.getName() != null) {
path.addAll(table.getName().getPath());
}
parent = parent.getParent();
break;
}
path.add(keyName);
return path;
parent = parent.getParent();
}
path.addAll(kv.getKey().getPath());
return path;
}

current = current.getParent();
Expand Down
123 changes: 123 additions & 0 deletions rewrite-toml/src/main/java/org/openrewrite/toml/TomlPaths.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright 2025 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.toml;

import org.jspecify.annotations.Nullable;
import org.openrewrite.toml.marker.ArrayTable;
import org.openrewrite.toml.marker.InlineTable;
import org.openrewrite.toml.tree.Toml;
import org.openrewrite.toml.tree.TomlValue;

import java.util.List;

/**
* Path-based lookup over a {@link Toml.Document}. Resolves a logical key path
* (a list of unquoted segment names) to the AST node for that key, regardless
* of whether the document expressed it as
* <ul>
* <li>a flat dotted key ({@code a.b.c.x = 1}),</li>
* <li>nested table headers ({@code [a.b.c] x = 1}),</li>
* <li>nested inline tables ({@code a = {b = {c = {x = 1}}}}), or</li>
* <li>any combination of the above (e.g., {@code [a.b] c.x = 1}).</li>
* </ul>
*
* <p>Quoted segments containing literal dots are treated as a single segment,
* so {@code site."google.com"} resolves at path {@code ["site", "google.com"]}
* (length 2), distinct from {@code site.google.com} which resolves at
* {@code ["site", "google", "com"]} (length 3).
*
* <p>Array tables ({@code [[products]]}) are not searched: there is no way to
* disambiguate which element of the array a path refers to.
*/
public final class TomlPaths {

private TomlPaths() {
}

/**
* Find the {@link Toml.KeyValue} at the given logical key path, or
* {@code null} if no such key exists.
*/
public static Toml.@Nullable KeyValue findKeyValue(Toml.Document doc, List<String> path) {
if (path.isEmpty()) {
return null;
}
return findKeyValueIn(doc.getValues(), path);
}

/**
* Find a standard (non-array, non-inline) {@link Toml.Table} whose header
* matches the given logical key path, or {@code null} if no such table
* exists. Implicit tables defined only via dotted keys (e.g. {@code [a.b]}
* implicitly defines {@code [a]}) are not returned — only tables that are
* explicitly written as {@code [path]} are matched.
*/
public static Toml.@Nullable Table findTable(Toml.Document doc, List<String> path) {
if (path.isEmpty()) {
return null;
}
for (TomlValue value : doc.getValues()) {
if (!(value instanceof Toml.Table)) {
continue;
}
Toml.Table table = (Toml.Table) value;
if (isStandardTable(table) && table.getName() != null && path.equals(table.getName().getPath())) {
return table;
}
}
return null;
}

private static Toml.@Nullable KeyValue findKeyValueIn(List<? extends Toml> elements, List<String> targetSuffix) {
for (Toml element : elements) {
if (element instanceof Toml.KeyValue) {
Toml.KeyValue kv = (Toml.KeyValue) element;
List<String> kvPath = kv.getKey().getPath();
if (kvPath.equals(targetSuffix)) {
return kv;
}
if (kv.getValue() instanceof Toml.Table) {
Toml.KeyValue found = recurseAtPrefix(kvPath, ((Toml.Table) kv.getValue()).getValues(), targetSuffix);
if (found != null) {
return found;
}
}
} else if (element instanceof Toml.Table) {
Toml.Table table = (Toml.Table) element;
if (!isStandardTable(table) || table.getName() == null) {
continue;
}
Toml.KeyValue found = recurseAtPrefix(table.getName().getPath(), table.getValues(), targetSuffix);
if (found != null) {
return found;
}
}
}
return null;
}

private static Toml.@Nullable KeyValue recurseAtPrefix(List<String> prefix, List<? extends Toml> children, List<String> targetSuffix) {
if (prefix.size() >= targetSuffix.size() || !targetSuffix.subList(0, prefix.size()).equals(prefix)) {
return null;
}
return findKeyValueIn(children, targetSuffix.subList(prefix.size(), targetSuffix.size()));
}

private static boolean isStandardTable(Toml.Table table) {
return !table.getMarkers().findFirst(InlineTable.class).isPresent() &&
!table.getMarkers().findFirst(ArrayTable.class).isPresent();
}
}
12 changes: 12 additions & 0 deletions rewrite-toml/src/main/java/org/openrewrite/toml/TomlVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.openrewrite.internal.ListUtils;
import org.openrewrite.toml.tree.Space;
import org.openrewrite.toml.tree.Toml;
import org.openrewrite.toml.tree.TomlKey;
import org.openrewrite.toml.tree.TomlRightPadded;
import org.openrewrite.toml.tree.TomlValue;

Expand Down Expand Up @@ -64,6 +65,13 @@ public Toml visitIdentifier(Toml.Identifier identifier, P p) {
return i.withMarkers(visitMarkers(i.getMarkers(), p));
}

public Toml visitDottedKey(Toml.DottedKey dottedKey, P p) {
Toml.DottedKey d = dottedKey;
d = d.withPrefix(visitSpace(d.getPrefix(), p));
d = d.withMarkers(visitMarkers(d.getMarkers(), p));
return d.getPadding().withNames(ListUtils.map(d.getPadding().getNames(), n -> visitRightPadded(n, p)));
}

public Toml visitKeyValue(Toml.KeyValue keyValue, P p) {
Toml.KeyValue kv = keyValue;
kv = kv.withPrefix(visitSpace(kv.getPrefix(), p));
Expand All @@ -86,6 +94,10 @@ public Toml visitTable(Toml.Table table, P p) {
Toml.Table t = table;
t = t.withPrefix(visitSpace(t.getPrefix(), p));
t = t.withMarkers(visitMarkers(t.getMarkers(), p));
TomlRightPadded<TomlKey> name = t.getPadding().getName();
if (name != null) {
t = t.getPadding().withName(visitRightPadded(name, p));
}
return t.withValues(ListUtils.map(t.getValues(), v -> visit(v, p)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,11 @@ public Toml.Document visitDocument(TomlParser.DocumentContext ctx) {
}

@Override
public Toml.Identifier visitKey(TomlParser.KeyContext ctx) {
return (Toml.Identifier) super.visitKey(ctx);
public TomlKey visitKey(TomlParser.KeyContext ctx) {
if (ctx.simpleKey() != null) {
return visitSimpleKey(ctx.simpleKey());
}
return visitDottedKey(ctx.dottedKey());
}

@Override
Expand All @@ -147,22 +150,16 @@ public Toml.Identifier visitSimpleKey(TomlParser.SimpleKeyContext ctx) {
}

@Override
public Toml.Identifier visitDottedKey(TomlParser.DottedKeyContext ctx) {
public Toml.DottedKey visitDottedKey(TomlParser.DottedKeyContext ctx) {
Space prefix = prefix(ctx);
StringBuilder text = new StringBuilder();
StringBuilder key = new StringBuilder();
for (ParseTree child : ctx.children) {
Space space = sourceBefore(child.getText());
text.append(space.getWhitespace()).append(child.getText());
key.append(child.getText());
List<TomlParser.SimpleKeyContext> simpleKeys = ctx.simpleKey();
List<TomlRightPadded<Toml.Identifier>> segments = new ArrayList<>(simpleKeys.size());
for (int i = 0; i < simpleKeys.size(); i++) {
Toml.Identifier segment = visitSimpleKey(simpleKeys.get(i));
Space after = i < simpleKeys.size() - 1 ? sourceBefore(".") : Space.EMPTY;
segments.add(TomlRightPadded.build(segment).withAfter(after));
}
return new Toml.Identifier(
randomId(),
prefix,
Markers.EMPTY,
text.toString(),
key.toString()
);
return new Toml.DottedKey(randomId(), prefix, Markers.EMPTY, segments);
}

/**
Expand Down Expand Up @@ -192,7 +189,7 @@ public Toml.KeyValue visitKeyValue(TomlParser.KeyValueContext ctx) {
randomId(),
prefix,
Markers.EMPTY,
TomlRightPadded.build((TomlKey) visitKey(c.key())).withAfter(sourceBefore("=")),
TomlRightPadded.build(visitKey(c.key())).withAfter(sourceBefore("=")),
visitValue(c.value())
));
}
Expand Down Expand Up @@ -413,8 +410,8 @@ public Toml visitInlineTable(TomlParser.InlineTableContext ctx) {
public Toml visitStandardTable(TomlParser.StandardTableContext ctx) {
return convert(ctx, (c, prefix) -> {
sourceBefore("[");
Toml.Identifier tableName = visitKey(c.key());
TomlRightPadded<Toml.Identifier> nameRightPadded = TomlRightPadded.build(tableName).withAfter(sourceBefore("]"));
TomlKey tableName = visitKey(c.key());
TomlRightPadded<TomlKey> nameRightPadded = TomlRightPadded.build(tableName).withAfter(sourceBefore("]"));

List<TomlParser.KeyValueContext> values = c.keyValue();
List<TomlRightPadded<Toml>> elements = new ArrayList<>();
Expand All @@ -436,8 +433,8 @@ public Toml visitStandardTable(TomlParser.StandardTableContext ctx) {
public Toml visitArrayTable(TomlParser.ArrayTableContext ctx) {
return convert(ctx, (c, prefix) -> {
sourceBefore("[[");
Toml.Identifier tableName = visitKey(c.key());
TomlRightPadded<Toml.Identifier> nameRightPadded = TomlRightPadded.build(tableName).withAfter(sourceBefore("]]"));
TomlKey tableName = visitKey(c.key());
TomlRightPadded<TomlKey> nameRightPadded = TomlRightPadded.build(tableName).withAfter(sourceBefore("]]"));

List<TomlParser.KeyValueContext> values = c.keyValue();
List<TomlRightPadded<Toml>> elements = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ public Toml visitIdentifier(Toml.Identifier identifier, PrintOutputCapture<P> p)
return identifier;
}

@Override
public Toml visitDottedKey(Toml.DottedKey dottedKey, PrintOutputCapture<P> p) {
beforeSyntax(dottedKey, p);
visitRightPadded(dottedKey.getPadding().getNames(), ".", p);
afterSyntax(dottedKey, p);
return dottedKey;
}

@Override
public Toml visitKeyValue(Toml.KeyValue keyValue, PrintOutputCapture<P> p) {
beforeSyntax(keyValue, p);
Expand Down
Loading