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
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.gradle.customchecks;

import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;

import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.matchers.Description;
import com.sun.source.doctree.DocCommentTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.PackageTree;
import com.sun.tools.javac.api.JavacTrees;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import javax.lang.model.element.Modifier;

@BugPattern(
summary =
"This public impl class is missing the required javadoc disclaimer: \""
+ OtelImplJavadoc.EXPECTED_IMPL_COMMENT
+ "\"",
severity = WARNING,
linkType = NONE)
public class OtelImplJavadoc extends BugChecker implements BugChecker.ClassTreeMatcher {

private static final long serialVersionUID = 1L;

private static final Pattern IMPL_PACKAGE_PATTERN = Pattern.compile("\\bimpl\\b");

static final String EXPECTED_IMPL_COMMENT =
"This class is not intended for use by application developers."
+ " Its API is stable and will not be changed or removed in a backwards-incompatible"
+ " manner.";

@Override
public Description matchClass(ClassTree tree, VisitorState state) {
if (!isPublic(tree) || !isImpl(state) || tree.getSimpleName().toString().endsWith("Test")) {
return Description.NO_MATCH;
}
String javadoc = getJavadoc(state);
if (javadoc != null && javadoc.contains(EXPECTED_IMPL_COMMENT)) {
return Description.NO_MATCH;
}
return describeMatch(tree);
}

private static boolean isPublic(ClassTree tree) {
return tree.getModifiers().getFlags().contains(Modifier.PUBLIC);
}

private static boolean isImpl(VisitorState state) {
PackageTree packageTree = state.getPath().getCompilationUnit().getPackage();
if (packageTree == null) {
return false;
}
String packageName = state.getSourceForNode(packageTree.getPackageName());
return packageName != null && IMPL_PACKAGE_PATTERN.matcher(packageName).find();
}

@Nullable
private static String getJavadoc(VisitorState state) {
DocCommentTree docCommentTree =
JavacTrees.instance(state.context).getDocCommentTree(state.getPath());
if (docCommentTree == null) {
return null;
}
return docCommentTree.toString().replace("\n", " ").replace(" * ", " ").replaceAll("\\s+", " ");
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
io.opentelemetry.gradle.customchecks.OtelDeprecatedApiUsage
io.opentelemetry.gradle.customchecks.OtelImplJavadoc
io.opentelemetry.gradle.customchecks.OtelInternalJavadoc
io.opentelemetry.gradle.customchecks.OtelPrivateConstructorForUtilityClass
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.gradle.customchecks;

import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;

class OtelImplJavadocTest {

@Test
void positiveCases() {
CompilationTestHelper.newInstance(OtelImplJavadoc.class, OtelImplJavadocTest.class)
.addSourceLines(
"impl/ImplJavadocPositiveCases.java",
"/*",
" * Copyright The OpenTelemetry Authors",
" * SPDX-License-Identifier: Apache-2.0",
" */",
"package io.opentelemetry.gradle.customchecks.impl;",
"// BUG: Diagnostic contains: missing the required javadoc disclaimer",
"public class ImplJavadocPositiveCases {",
" // BUG: Diagnostic contains: missing the required javadoc disclaimer",
" public static class One {}",
" /** Doesn't have the disclaimer. */",
" // BUG: Diagnostic contains: missing the required javadoc disclaimer",
" public static class Two {}",
"}")
.doTest();
}

@Test
void negativeCases() {
CompilationTestHelper.newInstance(OtelImplJavadoc.class, OtelImplJavadocTest.class)
.addSourceLines(
"impl/ImplJavadocNegativeCases.java",
"/*",
" * Copyright The OpenTelemetry Authors",
" * SPDX-License-Identifier: Apache-2.0",
" */",
"package io.opentelemetry.gradle.customchecks.impl;",
"/**",
" * This class is not intended for use by application developers. Its API is stable and",
" * will not be changed or removed in a backwards-incompatible manner.",
" */",
"public class ImplJavadocNegativeCases {",
" /**",
" * This class is not intended for use by application developers. Its API is stable",
" * and will not be changed or removed in a backwards-incompatible manner.",
" */",
" public static class One {}",
" // Non-public class without disclaimer is fine.",
" static class Two {}",
"}")
.doTest();
}

@Test
void nonImplPackageIgnored() {
CompilationTestHelper.newInstance(OtelImplJavadoc.class, OtelImplJavadocTest.class)
.addSourceLines(
"other/NonImplPackageCases.java",
"/*",
" * Copyright The OpenTelemetry Authors",
" * SPDX-License-Identifier: Apache-2.0",
" */",
"package io.opentelemetry.gradle.customchecks.other;",
"public class NonImplPackageCases {",
" public static class One {}",
"}")
.doTest();
}
}
16 changes: 8 additions & 8 deletions docs/knowledge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ is the signal.

## Topics

| File | Load when |
| --- | --- |
| [build.md](build.md) | Always — build requirements and common tasks |
| [general-patterns.md](general-patterns.md) | Always — style, nullability, visibility, AutoValue, locking, logging |
| [api-stability.md](api-stability.md) | Public API additions, removals, renames, or deprecations; stable vs alpha compatibility |
| [gradle-conventions.md](gradle-conventions.md) | `build.gradle.kts` or `settings.gradle.kts` changes; new modules |
| [testing-patterns.md](testing-patterns.md) | Test files in scope — assertions, test utilities, test suites |
| [other-tasks.md](other-tasks.md) | Dev environment setup, benchmarks, composite builds, native image tests, OTLP protobuf updates |
| File | Load when |
|------------------------------------------------|------------------------------------------------------------------------------------------------|
| [build.md](build.md) | Always — build requirements and common tasks |
| [general-patterns.md](general-patterns.md) | Always — style, nullability, visibility, AutoValue, locking, logging, internal & impl packages |
| [api-stability.md](api-stability.md) | Public API additions, removals, renames, or deprecations, stable vs alpha compatibility |
| [gradle-conventions.md](gradle-conventions.md) | `build.gradle.kts` or `settings.gradle.kts` changes; new modules |
| [testing-patterns.md](testing-patterns.md) | Test files in scope — assertions, test utilities, test suites |
| [other-tasks.md](other-tasks.md) | Dev environment setup, benchmarks, composite builds, native image tests, OTLP protobuf updates |

## Conventions

Expand Down
26 changes: 26 additions & 0 deletions docs/knowledge/general-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,29 @@ most violations automatically.
- **EditorConfig** (`.editorconfig`) — configures IntelliJ to match project style automatically.
It doesn't cover all rules, so `spotlessApply` is still required.

## Impl (Implementation) code

Use `*.impl.*` sub-packages for code that must be `public` to be shared across OpenTelemetry
modules, but is not intended for use by application developers. This is the right choice when
`*.internal.*` is too restrictive — for example, a utility used by both the API and SDK modules
that needs a stable contract across module boundaries.

Unlike `*.internal.*` packages, `*.impl.*` packages carry full backwards-compatibility guarantees
and are included in `japicmp` compatibility checks.

Public classes in `impl` packages must carry the following disclaimer (enforced by the
`OtelImplJavadoc` Error Prone check in `custom-checks/`):

```java
/**
* This class is not intended for use by application developers. Its API is stable and will not
* be changed or removed in a backwards-incompatible manner.
*/
```

See also [Internal code](#internal-code) for code that is not for application developers and has
no stability guarantees.

## Internal code

Prefer package-private over putting code in an `internal` package. Use `internal` only when the
Expand Down Expand Up @@ -93,6 +116,9 @@ Internal code must not be used across module boundaries — module `foo` must no
code from module `bar`. Cross-module internal usage is a known issue being tracked and cleaned up
in open-telemetry/opentelemetry-java#6970.

See also [Impl (Implementation) code](#impl-implementation-code) for cross-module code that is not for application developers but
requires a stable API.

## Javadoc

All public classes, and their public and protected methods, must have complete Javadoc including
Expand Down
Loading