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
15 changes: 15 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Agent Instructions

## Java Test Style

* Use Hamcrest `assertThat` assertions instead of basic JUnit assertions such
as `assertEquals`, `assertTrue`, or `assertFalse`.
* Group multiple assertions in one test with JUnit Jupiter's `assertAll`.
* Import Java classes and refer to their simple class names; do not use fully
qualified names such as `java.util.List` in source code.

## OpenFastTrace Coverage

* Do not add `itest` items or `[itest->…]` coverage tags to Markdown files.
Add integration-test coverage tags directly to the relevant source test
files instead.
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ This first lexical highlighting slice does not validate declarations. Coverage
tags in source code will be highlighted in a follow-up PR using LSP semantic
tokens.

### OFT coverage-tag highlighting

Valid long-form coverage tags, such as `[impl->dsn~editor-presentation~3]`,
are semantically highlighted in source, configuration, and markup files
supported by OpenFastTrace. This does not validate that the target exists; it
only recognizes the tag syntax while preserving the host language mode. See
[Known Issues](#known-issues) for language-server compatibility limitations.

## Requirements

The extension requires Java 21 or later. It uses `openfasttrace.java.home` when
Expand All @@ -61,7 +69,18 @@ runtime through `JAVA_HOME` or `PATH`.

## Known Issues

Calling out known issues can help limit users opening duplicate issues against your extension.
Coverage-tag highlighting is not reliable in Java and may be unavailable in
other file types when their VS Code language extension provides a more-specific
semantic-token provider. VS Code uses that provider instead of the OFT provider
for the whole document, so the OFT tag can briefly appear highlighted and then
revert to an ordinary comment. TypeScript currently works because the OFT
provider wins the selection there.

The planned solution is to apply editor decorations to parser-confirmed OFT
tag ranges. Decorations overlay the host language's highlighting instead of
competing with its semantic-token provider. Definition and reference navigation
are not subject to this limitation: VS Code can merge their results from OFT
and the host language server.

## Release Notes

Expand Down
61 changes: 61 additions & 0 deletions doc/changesets/05-semantic-coverage-tag-highlighting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# 05 Semantic Coverage-Tag Highlighting

## Goal

Highlight valid OpenFastTrace coverage tags in files supported by the tag
importer while preserving the host language's normal syntax highlighting.

## Scope

In scope:

* Provide `textDocument/semanticTokens/full` from the Java language server.
* Track opened documents and recognize valid long-form coverage tags.
* Map the semantic token to an OpenFastTrace VS Code presentation scope.

Out of scope:

* Short, path-configured tags, validation diagnostics, indexing, navigation,
completion, and trace execution.

## Design References

* [System Requirements](../system_requirements.md)
* [LSP-first Architecture](../design/architecture.md)
* [Quality Requirements](../design/quality_requirements.md)

## Task List

### Requirements And Design

- [x] Confirm `req~highlight-coverage-tags~1` and
`scn~recognize-and-highlight-oft-content~1` cover this behavior.
- [x] Add traced implementation and integration-test items beneath
`dsn~editor-presentation~3`.
- [x] Add the complete recognition and editor-presentation chain to the MVP
trace.

### Implementation

- [x] Advertise semantic-token support, retain opened document text, and emit
tokens for valid long-form coverage tags in supported file types.
- [x] Extend the language client selector and map the custom semantic token to
the OpenFastTrace coverage-tag scope.

### Verification

- [x] Add Java unit/protocol tests and VS Code integration coverage for valid,
malformed, changed, and unsupported-file content.
- [x] Run server tests, extension compile/lint/unit/integration tests, the full
Gradle build, and the OpenFastTrace requirements trace.

### Update User Documentation

- [x] Document coverage-tag highlighting and its long-form scope in README.md.

## Version and Changelog Update

- [ ] Check the current version against the latest GitHub release.
- [ ] Increment the semantic version for this feature.
- [ ] Add the release changelog entry, including the bundled OpenFastTrace
version from resolved Gradle dependency metadata.
61 changes: 56 additions & 5 deletions doc/design/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,28 @@ from each connected client. It is implemented with Eclipse LSP4J and serves
the protocol over standard input and output. Standard output is reserved for
protocol messages; diagnostics and operational logs are written elsewhere.

Standard navigation coexists with the host-language language server. VS Code
requests definition and reference results from applicable providers in parallel
and merges their locations. This lets OFT resolve a coverage tag to its OFT
declaration and find coverage tags for an OFT declaration without suppressing
Java or other host-language navigation.

Tags: mvp

Covers:
- `scn~start-and-negotiate-language-server~1`

Needs: impl

#### Open Issue: Authoritative OFT Parsing

The current coverage-tag recognition uses LSP-local parsing logic, including a
regular expression and a copied list of supported file extensions. Replace it
with the OpenFastTrace library's parser and tag-importer APIs. The language
server must obtain validation and ranges from that authoritative implementation
so its highlighting, navigation, and trace behavior cannot drift from
OpenFastTrace.

### VS Code Language Client
`dsn~vscode-language-client~2`

Expand Down Expand Up @@ -82,7 +97,7 @@ Covers:
- `impl~workspace-symbol-protocol-slice~1`

### Editor Presentation
`dsn~editor-presentation~3`
`dsn~editor-presentation~4`

The VS Code adapter contributes a TextMate injection grammar for immediate OFT
declaration coloring in Markdown and reStructuredText. It preserves the host
Expand All @@ -92,17 +107,53 @@ coverage-tag styling in source, configuration, and markup files.
TextMate grammar injection is intentionally not used for coverage tags. The
set of supported source, configuration, and markup languages would require
per-host grammar injection rules and lexical matching cannot reliably separate
valid coverage tags from lookalike text. The language server instead owns OFT
tag parsing and supplies semantic tokens, so coverage-tag styling is consistent
across host languages and can later reflect parsed validity and link resolution.
valid coverage tags from lookalike text.

The language server owns OFT tag parsing. Its semantic-token response is not a
reliable cross-language presentation mechanism: VS Code selects one
full-document semantic-token provider rather than merging their results. A
language-specific provider with a more specific document selector, such as the
Java extension, therefore replaces the OFT tokens after it activates. Raising
the OFT provider's selector score would instead suppress the host language's
semantic highlighting. The VS Code adapter must render parser-confirmed
coverage-tag ranges with editor decorations so they overlay, rather than
compete with, host language semantic highlighting.

Tags: backlog
Tags: mvp

Covers:
- `scn~recognize-and-highlight-oft-content~1`

Needs: impl

#### Open Issue: Coverage-Tag Syntax Highlighting

Coverage-tag syntax highlighting is not currently reliable in Java or in any
other supported file type for which an installed language extension supplies a
more-specific semantic-token provider. This can affect languages such as C#,
C/C++, Python, Go, Rust, YAML, JSON, HTML, and CSS; the exact set depends on
the extensions installed in VS Code. A semantic-token solution works only when
the OFT provider wins provider selection, as currently observed for TypeScript.
The open implementation work is to render parser-confirmed tag ranges with
editor decorations, preserving the host language's syntax and semantic
highlighting.

### Coverage-Tag Semantic Tokens
`impl~coverage-tag-semantic-tokens~1`

The language server tracks the current contents of opened files supported by
the OpenFastTrace tag importer and serves `textDocument/semanticTokens/full`.
It emits the standard `type` token only for syntactically valid
long-form coverage tags, letting VS Code apply the active theme's standard
semantic-token styling without replacing each document's host language grammar.

Tags: mvp

Covers:
- `dsn~editor-presentation~4`

Needs: itest

### OFT Template Snippets
`dsn~oft-template-snippets~1`

Expand Down
8 changes: 4 additions & 4 deletions doc/system_requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ only that scope.
The product recognizes OFT declarations in Markdown, reStructuredText, and
coverage tags in supported workspace files.

Tags: backlog
Tags: mvp

Needs: req

Expand Down Expand Up @@ -107,7 +107,7 @@ Needs: req
The editor highlights OFT declarations in `.md`, `.markdown`, and `.rst`
documents without changing the documents' ordinary language mode.

Tags: backlog
Tags: mvp

Covers:
- `feat~recognize-oft-content~1`
Expand All @@ -133,7 +133,7 @@ Needs: scn
The editor highlights OFT coverage tags in source, configuration, and markup
files supported by the OpenFastTrace tag importer.

Tags: backlog
Tags: mvp

Covers:
- `feat~recognize-oft-content~1`
Expand Down Expand Up @@ -309,7 +309,7 @@ Needs: scn
**Then** the extension highlights declarations or coverage tags in the relevant
range while preserving the file's normal language support.

Tags: backlog
Tags: mvp

Covers:
- `req~highlight-oft-declarations~1`
Expand Down
18 changes: 14 additions & 4 deletions extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,26 @@ import { resolveJavaExecutable } from './javaRuntime';
let client: LanguageClient | undefined;

export async function activate(context: vscode.ExtensionContext): Promise<{ getLanguageClient: typeof getLanguageClient }> {
const outputChannel = vscode.window.createOutputChannel('OpenFastTrace', { log: true });
context.subscriptions.push(outputChannel);
try {
outputChannel.info('Resolving Java runtime.');
const configuredJavaHome = vscode.workspace.getConfiguration('openfasttrace').get<string>('java.home');
const javaExecutable = await resolveJavaExecutable(configuredJavaHome || undefined, process.env);
outputChannel.info(`Starting language server with ${javaExecutable}.`);
client = new LanguageClient(
'openfasttraceLanguageServer',
'OpenFastTrace Language Server',
serverOptions(context, javaExecutable),
clientOptions,
clientOptions(outputChannel),
);
context.subscriptions.push(client);
await client.start();
outputChannel.info('OpenFastTrace extension activated.');
return { getLanguageClient };
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
outputChannel.error(`Unable to start the OpenFastTrace language server: ${message}`);
void vscode.window.showErrorMessage(`Unable to start the OpenFastTrace language server: ${message}`);
throw error;
}
Expand Down Expand Up @@ -49,6 +55,10 @@ function serverEnvironment(javaExecutable: string): NodeJS.ProcessEnv {
return { ...process.env, JAVA_HOME: path.dirname(path.dirname(javaExecutable)) };
}

const clientOptions: LanguageClientOptions = {
documentSelector: ['markdown', 'restructuredtext'],
};
function clientOptions(outputChannel: vscode.LogOutputChannel): LanguageClientOptions {
return {
documentSelector: [{ scheme: 'file' }],
outputChannel,
};
}

29 changes: 28 additions & 1 deletion extension/src/test/integration/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import * as vscode from 'vscode';
// [itest->dsn~oft-template-snippets~1]
interface OpenFastTraceExports {
getLanguageClient(): {
sendRequest(method: string, parameters: { query: string }): Thenable<unknown>;
sendRequest(method: string, parameters: unknown): Thenable<unknown>;
sendNotification(method: string, parameters: unknown): Thenable<void>;
} | undefined;
}

Expand Down Expand Up @@ -125,6 +126,28 @@ suite('OpenFastTrace extension', () => {
assert.equal(captures['4'].name, 'constant.numeric.openfasttrace.revision');
});

// [itest->impl~coverage-tag-semantic-tokens~1]
test('returns coverage-tag semantic tokens from the live language server', async () => {
const extension = vscode.extensions.getExtension<OpenFastTraceExports>('itsallcode.openfasttrace');
assert.ok(extension, 'OpenFastTrace extension must be available to the test host');
await extension.activate();
const client = extension.exports.getLanguageClient();
assert.ok(client, 'OpenFastTrace language client must be started');

await client.sendNotification('textDocument/didOpen', {
textDocument: {
uri: 'file:///workspace/coverage-tag.java', languageId: 'java', version: 1,
text: `// ${coverageTag('dsn~editor-presentation~3')}\n// ${coverageTag('dsn~1invalid~3')}`,
},
});

const tokens = await client.sendRequest('textDocument/semanticTokens/full', {
textDocument: { uri: 'file:///workspace/coverage-tag.java' },
}) as { data: number[] };

assert.deepStrictEqual(tokens.data, [0, 3, 33, 0, 0]);
});

for (const id of matchingOftIds) {
test(`recognizes ${id}`, async () => {
const grammar = await openFastTraceGrammar();
Expand Down Expand Up @@ -173,3 +196,7 @@ function backtickedDeclarationPattern(grammar: { definition: TextMateGrammar }):
function declarationPattern(grammar: { definition: TextMateGrammar }): RegExp {
return new RegExp(grammar.definition.patterns[1].match);
}

function coverageTag(target: string): string {
return `[impl${'->'}${target}]`;
}
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
lsp4j = "1.0.0"
openfasttrace = "4.4.0"
junit = "5.13.4"
hamcrest = "3.0"

[libraries]
lsp4j = { group = "org.eclipse.lsp4j", name = "org.eclipse.lsp4j", version.ref = "lsp4j" }
openfasttrace = { group = "org.itsallcode.openfasttrace", name = "openfasttrace", version.ref = "openfasttrace" }
hamcrest = { group = "org.hamcrest", name = "hamcrest", version.ref = "hamcrest" }
1 change: 1 addition & 0 deletions server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ testing {
test {
useJUnitJupiter(libs.versions.junit.get())
dependencies {
implementation libs.hamcrest
}
}
}
Expand Down
Loading