Skip to content
Merged
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
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ JitPack continue to resolve through the existing coordinates.
`./mvnw -DskipTests -P japicmp verify -pl .`; HTML/MD/XML reports
land in `target/japicmp/`. JitPack repository is scoped to the
`japicmp` profile, so downstream consumers do not inherit it.
- **Class-level `@since 1.0.0` Javadoc on the public entry-point
surface** (Track H1). 26 public types in the canonical user-reached
packages (`com.demcha.compose.GraphCompose`, `com.demcha.compose.document.api.{DocumentSession, DocumentPageSize, PageBackgroundFill}`,
`com.demcha.compose.document.dsl.{DocumentDsl, RichText, Transformable}` plus all 19 DSL builders)
now carry class-level `@since 1.0.0` Javadoc tags so callers can see
the introduction version at IDE quick-doc / generated Javadoc time
without trawling CHANGELOG history. New guard test
`PublicApiSinceTagCoverageTest` source-scans the three entry-point
roots and fails the build if a new public top-level type lands
without a class-level `@since` tag; `internal/` sub-packages are
excluded by convention (`InternalAnnotationCoverageTest` covers those).
Method-level `@since` backfill for the ~380 public methods in these
packages is intentionally out of scope here and tracked separately.

### Engine internals (no behaviour change)

Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/demcha/compose/GraphCompose.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
* </ol>
*
* @author Artem Demchyshyn
* @since 1.0.0
*
* <h3>Build a PDF file with the canonical DSL</h3>
* <pre>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*
* @param width page width in points
* @param height page height in points
* @since 1.0.0
*/
public record DocumentPageSize(double width, double height) {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
* <p><b>Thread-safety:</b> this type is mutable and not thread-safe.</p>
*
* @author Artem Demchyshyn
* @since 1.0.0
*/
public final class DocumentSession implements AutoCloseable {
private static final Logger LIFECYCLE_LOG = LoggerFactory.getLogger("com.demcha.compose.document.lifecycle");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
* Keep {@code yRatio + heightRatio <= 1.0} so the fill
* stays within the page.
* @param color fill color (required)
* @since 1.0.0
*/
public record PageBackgroundFill(double xRatio,
double yRatio,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*
* @param <T> concrete builder type
* @param <N> concrete node type
* @since 1.0.0
*/
public abstract class AbstractFlowBuilder<T extends AbstractFlowBuilder<T, N>, N extends DocumentNode> {
private String name = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

/**
* Builder for semantic barcode and QR-code nodes.
* @since 1.0.0
*/
public final class BarcodeBuilder implements Transformable<BarcodeBuilder> {
private String name = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
* positive {@code y} is down.</p>
*
* @author Artem Demchyshyn
* @since 1.0.0
*/
public final class CanvasLayerBuilder {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@

/**
* Builder for thin horizontal divider nodes.
* @since 1.0.0
*/
public final class DividerBuilder extends ShapeBuilder {
DividerBuilder() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
* }</pre>
*
* @author Artem Demchyshyn
* @since 1.0.0
*/
public final class DocumentDsl {
private final DocumentSession session;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* Builder for semantic circle and ellipse nodes.
*
* @author Artem Demchyshyn
* @since 1.0.0
*/
public final class EllipseBuilder implements Transformable<EllipseBuilder> {
private String name = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* Builder for semantic image nodes.
*
* @author Artem Demchyshyn
* @since 1.0.0
*/
public final class ImageBuilder implements Transformable<ImageBuilder> {
private String name = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* block by the canonical paginator.</p>
*
* @author Artem Demchyshyn
* @since 1.0.0
*/
public final class LayerStackBuilder {
private String name = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* Builder for fixed-size semantic line nodes.
*
* @author Artem Demchyshyn
* @since 1.0.0
*/
public final class LineBuilder implements Transformable<LineBuilder> {
private String name = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

/**
* Builder for semantic list nodes with marker and spacing controls.
* @since 1.0.0
*/
public final class ListBuilder {
private String name = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
* Builder for named semantic modules with optional title content.
*
* @author Artem Demchyshyn
* @since 1.0.0
*/
public final class ModuleBuilder extends AbstractFlowBuilder<ModuleBuilder, SectionNode> {
private String title = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@

/**
* Builder for explicit page-break control nodes.
* @since 1.0.0
*/
public final class PageBreakBuilder {
private String name = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
* Builder for root page-flow containers that attach to a document session.
*
* @author Artem Demchyshyn
* @since 1.0.0
*/
public final class PageFlowBuilder extends AbstractFlowBuilder<PageFlowBuilder, ContainerNode> {
private final DocumentSession session;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

/**
* Builder for semantic paragraph nodes and inline text runs.
* @since 1.0.0
*/
public final class ParagraphBuilder {
private String name = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
* }</pre>
*
* @author Artem Demchyshyn
* @since 1.0.0
*/
public final class RichText {
private final List<InlineRun> runs = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
* atomic pagination.</p>
*
* @author Artem Demchyshyn
* @since 1.0.0
*/
public final class RowBuilder {
private String name = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
* Builder for semantic sections inside document flows.
*
* @author Artem Demchyshyn
* @since 1.0.0
*/
public final class SectionBuilder extends AbstractFlowBuilder<SectionBuilder, SectionNode> {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
* Builder for rectangle-like semantic shape nodes.
*
* @author Artem Demchyshyn
* @since 1.0.0
*/
public class ShapeBuilder implements Transformable<ShapeBuilder> {
protected String name = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
* nine {@link LayerAlign} anchors plus optional on-screen offset.</p>
*
* @author Artem Demchyshyn
* @since 1.0.0
*/
public final class ShapeContainerBuilder implements Transformable<ShapeContainerBuilder> {
private String name = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* Builder for invisible fixed-size spacer nodes.
*
* @author Artem Demchyshyn
* @since 1.0.0
*/
public final class SpacerBuilder {
private String name = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

/**
* Builder for semantic table nodes.
* @since 1.0.0
*/
public final class TableBuilder {
private String name = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* can chain naturally
*
* @author Artem Demchyshyn
* @since 1.0.0
*/
public interface Transformable<T extends Transformable<T>> {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.demcha.documentation;

import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Asserts every {@code public} type in the canonical authoring entry-point
* packages carries a class-level {@code @since} Javadoc tag. The guard is
* intentionally narrow — it covers only the surfaces a user first reaches
* for ({@link com.demcha.compose.GraphCompose} factory, the
* {@code document.api} session / builder seam, and the {@code document.dsl}
* authoring builders) — not the whole public surface. A broader sweep
* across {@code document.node}, {@code document.style}, and the template
* packages is tracked separately.
*
* <p>The check is source-level rather than reflective because
* {@code @since} is a Javadoc tag, not a runtime annotation. The guard
* scans each {@code *.java} file and verifies that an {@code @since}
* token appears somewhere before the first {@code public class},
* {@code public final class}, {@code public sealed class}, {@code public
* abstract class}, {@code public interface}, {@code public sealed
* interface}, {@code public record}, {@code public final record}, or
* {@code public enum} declaration.</p>
*
* <p>Files with no public top-level type ({@code package-info.java} and
* the like) are skipped.</p>
*/
class PublicApiSinceTagCoverageTest {

private static final Path PROJECT_ROOT = Paths.get(".").toAbsolutePath().normalize();

/**
* The narrow set of "entry-point" sources this guard scans. New
* packages should be added here only when they qualify as primary
* user-reached surface; otherwise leave them for the broader sweep
* tracked under the v1.6.6 H-track.
*/
private static final List<Path> ROOTS = List.of(
PROJECT_ROOT.resolve("src/main/java/com/demcha/compose/GraphCompose.java"),
PROJECT_ROOT.resolve("src/main/java/com/demcha/compose/document/api"),
PROJECT_ROOT.resolve("src/main/java/com/demcha/compose/document/dsl")
);

/**
* Files explicitly excused from the {@code @since} requirement.
* Add an entry here only when the file truly does not declare a
* public top-level type that callers can reach — e.g. an internal
* helper that ended up in an entry-point package by accident and
* is on its way out.
*/
private static final Set<String> ALLOWLIST = Set.of();

private static final Pattern FIRST_PUBLIC_TYPE = Pattern.compile(
"(?m)^public\\s+(?:final\\s+|abstract\\s+|sealed\\s+|non-sealed\\s+)*"
+ "(?:class|interface|record|enum|@interface)\\b");

@Test
void publicEntryPointFilesCarryClassLevelSinceTag() throws IOException {
List<String> missing = new ArrayList<>();
for (Path root : ROOTS) {
scan(root, missing);
}
assertThat(missing)
.as("public entry-point files missing class-level @since tag")
.isEmpty();
}

private void scan(Path root, List<String> missing) throws IOException {
if (!Files.exists(root)) {
// Source layout change — let the test fail loudly so the
// maintainer notices the dropped root rather than silently
// skipping coverage.
throw new IllegalStateException("Guard root does not exist: " + root);
}
if (Files.isRegularFile(root)) {
checkFile(root, missing);
return;
}
try (Stream<Path> paths = Files.walk(root)) {
paths.filter(Files::isRegularFile)
.filter(p -> p.getFileName().toString().endsWith(".java"))
.filter(p -> !p.getFileName().toString().equals("package-info.java"))
// Files inside a `.internal` subpackage are internal by
// package-naming convention; treat the same way as the
// package-level @Internal annotation in `document.layout`.
// Coverage on those packages is enforced separately by
// InternalAnnotationCoverageTest.
.filter(p -> !p.toString().replace('\\', '/').contains("/internal/"))
.forEach(p -> checkFile(p, missing));
}
}

private void checkFile(Path file, List<String> missing) {
String relative = PROJECT_ROOT.relativize(file).toString().replace('\\', '/');
if (ALLOWLIST.contains(relative)) {
return;
}
String content;
try {
content = Files.readString(file);
} catch (IOException e) {
throw new RuntimeException("Failed to read " + file, e);
}
Matcher m = FIRST_PUBLIC_TYPE.matcher(content);
if (!m.find()) {
// Source file has no public top-level type — skip silently.
return;
}
String beforeFirstPublicType = content.substring(0, m.start());
if (!beforeFirstPublicType.contains("@since")) {
missing.add(relative);
}
}
}