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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,12 @@ if (boundaryLocation is not null)

## Custom Merge Strategy

### Merge identity

If your merge strategy inlines/expands directives (e.g., `@import`, `#include`), use the authoritative dependency
identity returned by your `IResourceResolver<TContent>`. TinyPreprocessor provides this mapping to merge via
`MergeContext<TContent, TDirective>.ResolvedReferences`.

Implement `IMergeStrategy<TContent, TDirective, TContext>` for custom output formatting:

```csharp
Expand All @@ -237,6 +243,11 @@ public sealed class JsonMergeStrategy : IMergeStrategy<ReadOnlyMemory<char>, Inc
// mergeContext.SourceMapBuilder.AddOffsetSegment(resourceId, generatedStartOffset, originalStartOffset, length)
// Use mergeContext.Diagnostics to report issues

// If you need to inline/expand directives:
// - Use mergeContext.ResolvedReferences to map (requestingResourceId, directiveIndex) -> resolved dependency ResourceId
// - Then fetch the already-processed dependency content from mergeContext.ResolvedCache
// Do NOT re-derive ResourceIds from raw reference strings (resolvers may implement custom mapping).

return ReadOnlyMemory<char>.Empty;
}
}
Expand Down
25 changes: 11 additions & 14 deletions docs/02-diagnostics-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,29 +161,26 @@ diagnostics = new DiagnosticCollection()
result = await resolver.ResolveAsync(reference, currentResource, ct)
if not result.IsSuccess:
diagnostics.Add(result.Error)

// During merge
// Merge strategies should only add diagnostics for merge-time concerns (e.g., directive placement rules,
// unsupported constructs, or missing data needed to build output), not for reference resolution.
// Continue processing other directives
// During cycle detection
for each cycle in graph.DetectCycles():
diagnostics.Add(new CircularDependencyDiagnostic(cycle))
```

### Resolution vs. Merge Responsibility

TinyPreprocessor separates responsibilities between phases:

- **Resolution diagnostics** (e.g., `TPP0100` `ResolutionFailedDiagnostic`) are emitted when
`IResourceResolver.ResolveAsync(reference, relativeTo)` cannot produce an `IResource`.
`IResourceResolver.ResolveAsync(reference, relativeTo)` cannot produce an `IResource`.
- **Merge diagnostics** (e.g., `TPP0300` `NonWholeLineDirectiveDiagnostic`) are emitted when merge logic detects
issues while producing output.
issues while producing output.

Merge strategies that inline/expand directives must use the authoritative resolved dependency identity provided via
`MergeContext.ResolvedReferences`. They must not re-derive a `ResourceId` from the raw reference string using
path heuristics, because resolvers may implement custom mapping.
Merge strategies should only add diagnostics for merge-time concerns (e.g., directive placement rules, unsupported
constructs, or missing data needed to build output), not for reference resolution.

// During cycle detection
for each cycle in graph.DetectCycles():
diagnostics.Add(new CircularDependencyDiagnostic(cycle))
```
Merge strategies that inline/expand directives must use the authoritative resolved dependency identity provided via
`MergeContext.ResolvedReferences`. They must not re-derive a `ResourceId` from the raw reference string using path
heuristics, because resolvers may implement custom mapping.

### Checking Results

Expand Down
4 changes: 3 additions & 1 deletion docs/05-merge-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class MergeContext<TContent, TDirective>
SourceMapBuilder : SourceMapBuilder // for recording mappings
Diagnostics : DiagnosticCollection // for reporting issues
ResolvedCache : IReadOnlyDictionary<ResourceId, IResource<TContent>> // for cross-referencing
ResolvedReferences : IReadOnlyDictionary<ResolvedReferenceKey, ResourceId> // directive occurrence -> resolved dependency id
ResolvedReferences : IReadOnlyDictionary<ResolvedReferenceKey, ResourceId> // directive occurrence -> resolved dependency id
DirectiveModel : IDirectiveModel<TDirective> // for interpreting directive locations
ContentModel : IContentModel<TContent> // for interpreting offsets + slicing content
```
Expand All @@ -62,6 +62,8 @@ dependency content. For these strategies, the **only** correct way to identify a
- `directiveIndex` is the zero-based index of the directive in that resource's parsed directive list
- **Value**: the resolved target `ResourceId` returned by the resolver

In code, the key type is `MergeContext<TContent, TDirective>.ResolvedReferenceKey`.

If a directive failed to resolve, it has **no entry** in this map.

---
Expand Down
2 changes: 1 addition & 1 deletion docs/06-preprocessor-orchestrator.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class Preprocessor<TContent, TDirective, TContext>
graph ← new ResourceDependencyGraph()
cache ← new Dictionary<ResourceId, ResolvedResource>()
sourceMapBuilder ← new SourceMapBuilder()
resolvedReferences ← new Dictionary<(ResourceId, directiveIndex), ResourceId>()
resolvedReferences ← new Dictionary<MergeContext.ResolvedReferenceKey, ResourceId>()

// Phase 1: Recursive resolution
await ResolveRecursiveAsync(root, depth: 0, ...)
Expand Down
Loading