@miliastry/quasar 1.0.6 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/Visuals/lyne.css +374 -9
- package/dist/Visuals/osu.css +393 -11
- package/dist/index.js +10470 -3336
- package/dist/index.mjs +10336 -3337
- package/dist/types/Analysis/Contracts/AnalysisReport.d.ts +22 -0
- package/dist/types/Analysis/Contracts/Contribution.d.ts +68 -0
- package/dist/types/Analysis/Contracts/Pass.d.ts +59 -0
- package/dist/types/Analysis/Contracts/PipelineContext.d.ts +34 -0
- package/dist/types/Analysis/Passes/Analysis/ColorUsageAnalyzer.d.ts +56 -0
- package/dist/types/Analysis/Passes/Analysis/GradientAnalyzer.d.ts +111 -0
- package/dist/types/Analysis/Passes/Analysis/MergeableColorAnalyzer.d.ts +26 -0
- package/dist/types/Analysis/Passes/Analysis/RainbowAnalyzer.d.ts +47 -0
- package/dist/types/Analysis/Passes/Analysis/SymbolAnalyzer.d.ts +89 -0
- package/dist/types/Analysis/Passes/Analysis/WaveAnalyzer.d.ts +44 -0
- package/dist/types/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Decision/DefaultDecision.d.ts +30 -0
- package/dist/types/Analysis/Passes/Decision/PaletteRemapDecision.d.ts +169 -0
- package/dist/types/Analysis/Passes/Decision/__tests__/DefaultDecision.test.d.ts +4 -0
- package/dist/types/Analysis/Passes/Transform/CollapseGradientTransform.d.ts +37 -0
- package/dist/types/Analysis/Passes/Transform/MergeColorsTransform.d.ts +28 -0
- package/dist/types/Analysis/Passes/Transform/RainbowCollapseTransform.d.ts +19 -0
- package/dist/types/Analysis/Passes/Transform/WaveCollapseTransform.d.ts +20 -0
- package/dist/types/Analysis/Passes/Utility/CharacterCountAnalyzer.d.ts +19 -0
- package/dist/types/Analysis/Pipeline/Pipeline.d.ts +69 -0
- package/dist/types/Analysis/Pipeline/PipelineBuilder.d.ts +31 -0
- package/dist/types/Analysis/Pipeline/PipelineStage.d.ts +17 -0
- package/dist/types/Analysis/Utils/color-utils.d.ts +60 -0
- package/dist/types/Analysis/__tests__/Integration.test.d.ts +8 -0
- package/dist/types/Analysis/__tests__/PaletteRemap.test.d.ts +14 -0
- package/dist/types/Analysis/__tests__/Pipeline.test.d.ts +10 -0
- package/dist/types/Analysis/__tests__/RoundTrip.test.d.ts +30 -0
- package/dist/types/Analysis/__tests__/SymbolAnalyzer.test.d.ts +11 -0
- package/dist/types/Analysis/index.d.ts +40 -0
- package/dist/types/BBCode/BBCodeDocumentModel.d.ts +102 -0
- package/dist/types/BBCode/BBCodeToGreenNode.d.ts +111 -0
- package/dist/types/BBCode/Parser.d.ts +78 -0
- package/dist/types/BBCode/index.d.ts +4 -0
- package/dist/types/Collab/positions.d.ts +55 -0
- package/dist/types/Commands/Command.d.ts +37 -0
- package/dist/types/Commands/CommandRegistry.d.ts +48 -0
- package/dist/types/Commands/DeleteNode.d.ts +7 -0
- package/dist/types/Commands/InsertText.d.ts +8 -0
- package/dist/types/Commands/SplitMerge.d.ts +9 -0
- package/dist/types/Commands/WrapInTag.d.ts +8 -0
- package/dist/types/Commands/index.d.ts +6 -0
- package/dist/types/Diff/TreeDiffer.d.ts +52 -0
- package/dist/types/Diff/__tests__/TreeDiffer.test.d.ts +1 -0
- package/dist/types/Diff/index.d.ts +2 -0
- package/dist/types/Edits/EditPlan.d.ts +144 -0
- package/dist/types/Edits/Optimizer.d.ts +78 -0
- package/dist/types/Edits/Rules/Rule.d.ts +91 -0
- package/dist/types/Edits/Rules/dropEmptyTags.d.ts +40 -0
- package/dist/types/Edits/Rules/dropRedundantNesting.d.ts +59 -0
- package/dist/types/Edits/Rules/index.d.ts +9 -0
- package/dist/types/Edits/Rules/mergeAdjacent.d.ts +93 -0
- package/dist/types/Edits/Rules/reorderWrappers.d.ts +42 -0
- package/dist/types/Edits/Rules/shortenHex.d.ts +30 -0
- package/dist/types/Edits/Rules/tagValue.d.ts +56 -0
- package/dist/types/Edits/Rules/unwrapInvisibleColor.d.ts +47 -0
- package/dist/types/Edits/applyEdits.d.ts +36 -0
- package/dist/types/Edits/index.d.ts +6 -0
- package/dist/types/Events/EventBus.d.ts +103 -0
- package/dist/types/Events/index.d.ts +2 -0
- package/dist/types/Formatter/Formatter.d.ts +32 -0
- package/dist/types/Formatter/index.d.ts +2 -0
- package/dist/types/HTML/HTMLDocumentModel.d.ts +13 -0
- package/dist/types/HTML/HTMLToGreenNode.d.ts +4 -0
- package/dist/types/Incremental/BracketIndex.d.ts +101 -0
- package/dist/types/Incremental/ChangeTracker.d.ts +69 -0
- package/dist/types/Incremental/IncrementalParser.d.ts +246 -0
- package/dist/types/Incremental/__tests__/BracketIndex.test.d.ts +1 -0
- package/dist/types/Incremental/__tests__/IncrementalParser.test.d.ts +1 -0
- package/dist/types/Incremental/index.d.ts +4 -0
- package/dist/types/Lexer/BBCodeLexer.d.ts +74 -0
- package/dist/types/Lexer/Lexer.d.ts +54 -0
- package/dist/types/Lexer/index.d.ts +4 -0
- package/dist/types/Linter/Linter.d.ts +58 -0
- package/dist/types/Linter/index.d.ts +2 -0
- package/dist/types/Markdown/MarkdownAST.d.ts +122 -0
- package/dist/types/Markdown/MarkdownDocumentModel.d.ts +15 -0
- package/dist/types/Markdown/MarkdownLexer.d.ts +155 -0
- package/dist/types/Markdown/MarkdownParser.d.ts +42 -0
- package/dist/types/Markdown/MarkdownToGreenNode.d.ts +6 -0
- package/dist/types/MilHibri/MilHibriDocumentModel.d.ts +21 -0
- package/dist/types/Model/DocumentModel.d.ts +318 -0
- package/dist/types/Model/NodeFactory.d.ts +29 -0
- package/dist/types/Model/TagRegistry.d.ts +165 -0
- package/dist/types/Model/index.d.ts +5 -0
- package/dist/types/Plugins/PluginAPI.d.ts +44 -0
- package/dist/types/Plugins/PluginRegistry.d.ts +81 -0
- package/dist/types/Plugins/index.d.ts +3 -0
- package/dist/types/Queries/QueryEngine.d.ts +32 -0
- package/dist/types/Queries/index.d.ts +1 -0
- package/dist/types/Reconciler/SurgicalReconciler.d.ts +23 -0
- package/dist/types/RenderPipeline/RenderPipeline.d.ts +46 -0
- package/dist/types/RenderPipeline/RenderTree.d.ts +55 -0
- package/dist/types/RenderPipeline/index.d.ts +4 -0
- package/dist/types/Repair/NestingRepair.d.ts +48 -0
- package/dist/types/Repair/index.d.ts +1 -0
- package/dist/types/Semantic/SemanticAnalyzer.d.ts +321 -0
- package/dist/types/Semantic/index.d.ts +2 -0
- package/dist/types/Symbols/SymbolTable.d.ts +47 -0
- package/dist/types/Symbols/index.d.ts +1 -0
- package/dist/types/Syntax/GreenNode.d.ts +154 -0
- package/dist/types/Syntax/GreenNodePool.d.ts +134 -0
- package/dist/types/Syntax/NodeMatcher.d.ts +75 -0
- package/dist/types/Syntax/RedNode.d.ts +280 -0
- package/dist/types/Syntax/RedNodeStore.d.ts +116 -0
- package/dist/types/Syntax/TreeBuilder.d.ts +54 -0
- package/dist/types/Syntax/__tests__/GreenNode.test.d.ts +1 -0
- package/dist/types/Syntax/__tests__/RedNode.test.d.ts +1 -0
- package/dist/types/Syntax/__tests__/RedNodeStore.test.d.ts +1 -0
- package/dist/types/Syntax/greenEdit.d.ts +64 -0
- package/dist/types/Syntax/hash.d.ts +17 -0
- package/dist/types/Syntax/index.d.ts +8 -0
- package/dist/types/Syntax/nodeAttr.d.ts +63 -0
- package/dist/types/Syntax/partition.d.ts +67 -0
- package/dist/types/Syntax/preserveNodeIds.d.ts +47 -0
- package/dist/types/Tokens/TokenResolver.d.ts +25 -0
- package/dist/types/Tokens/index.d.ts +1 -0
- package/dist/types/Transactions/Transaction.d.ts +36 -0
- package/dist/types/Transactions/UndoManager.d.ts +63 -0
- package/dist/types/Transactions/index.d.ts +3 -0
- package/dist/types/Transformers/ASTOptimizer.d.ts +34 -0
- package/dist/types/Transformers/GradientTransformer.d.ts +18 -0
- package/dist/types/Transformers/GrowTransformer.d.ts +16 -0
- package/dist/types/Transformers/RainbowTransformer.d.ts +17 -0
- package/dist/types/Transformers/SineWaveTransformer.d.ts +17 -0
- package/dist/types/Transformers/Transformer.d.ts +17 -0
- package/dist/types/Types/core.d.ts +104 -0
- package/dist/types/Types/diagnostics.d.ts +85 -0
- package/dist/types/Types/index.d.ts +11 -0
- package/dist/types/Types/operations.d.ts +109 -0
- package/dist/types/Types/queries.d.ts +67 -0
- package/dist/types/Types/symbols.d.ts +42 -0
- package/dist/types/Types/tokens.d.ts +75 -0
- package/dist/types/Utils/BBCodeGenerator.d.ts +21 -0
- package/dist/types/Utils/ColorMath.d.ts +92 -0
- package/dist/types/Utils/EffectMath.d.ts +643 -0
- package/dist/types/Utils/TagVocabulary.d.ts +104 -0
- package/dist/types/Utils/TagVocabulary.test.d.ts +1 -0
- package/dist/types/Utils/color.d.ts +29 -0
- package/dist/types/Utils/dom-to-svg.d.ts +28 -0
- package/dist/types/Utils/dom-to-svg.test.d.ts +1 -0
- package/dist/types/Utils/treeTransformers.d.ts +160 -0
- package/dist/types/Visitors/BBBlocksExporter.d.ts +27 -0
- package/dist/types/Visitors/BBCodeExporter.d.ts +90 -0
- package/dist/types/Visitors/BlockPatcher.d.ts +91 -0
- package/dist/types/Visitors/DOMMorpher.d.ts +19 -0
- package/dist/types/Visitors/HTMLRenderer.d.ts +492 -0
- package/dist/types/Visitors/JSONExporter.d.ts +42 -0
- package/dist/types/Visitors/MarkdownExporter.d.ts +28 -0
- package/dist/types/Visitors/SVGRenderer.d.ts +16 -0
- package/dist/types/Visitors/TiptapExporter.d.ts +40 -0
- package/dist/types/Visitors/Visitor.d.ts +35 -0
- package/dist/types/Visitors/index.d.ts +9 -0
- package/dist/types/Visuals/BoxDrawer.d.ts +54 -0
- package/dist/types/Visuals/LyneAudio.d.ts +8 -0
- package/dist/types/Visuals/index.d.ts +39 -0
- package/dist/types/index.d.ts +155 -0
- package/package.json +14 -10
- package/src/Analysis/Passes/Analysis/ColorUsageAnalyzer.ts +103 -0
- package/src/Analysis/Passes/Analysis/GradientAnalyzer.ts +294 -20
- package/src/Analysis/Passes/Analysis/SymbolAnalyzer.ts +257 -0
- package/src/Analysis/Passes/Decision/PaletteRemapDecision.ts +425 -0
- package/src/Analysis/Pipeline/Pipeline.ts +15 -2
- package/src/Analysis/index.ts +8 -2
- package/src/BBCode/BBCodeDocumentModel.ts +39 -1
- package/src/BBCode/BBCodeToGreenNode.ts +18 -12
- package/src/BBCode/Parser.ts +38 -1
- package/src/Edits/EditPlan.ts +290 -0
- package/src/Edits/Optimizer.ts +185 -0
- package/src/Edits/Rules/Rule.ts +145 -0
- package/src/Edits/Rules/dropEmptyTags.ts +110 -0
- package/src/Edits/Rules/dropRedundantNesting.ts +120 -0
- package/src/Edits/Rules/index.ts +28 -0
- package/src/Edits/Rules/mergeAdjacent.ts +224 -0
- package/src/Edits/Rules/reorderWrappers.ts +154 -0
- package/src/Edits/Rules/shortenHex.ts +79 -0
- package/src/Edits/Rules/tagValue.ts +105 -0
- package/src/Edits/Rules/unwrapInvisibleColor.ts +93 -0
- package/src/Edits/applyEdits.ts +54 -0
- package/src/Edits/index.ts +22 -0
- package/src/Events/EventBus.ts +18 -0
- package/src/HTML/HTMLToGreenNode.ts +193 -37
- package/src/Incremental/BracketIndex.ts +249 -0
- package/src/Incremental/IncrementalParser.ts +464 -41
- package/src/Lexer/BBCodeLexer.ts +25 -3
- package/src/Lexer/index.ts +1 -1
- package/src/Markdown/MarkdownAST.ts +65 -2
- package/src/Markdown/MarkdownLexer.ts +69 -14
- package/src/Markdown/MarkdownParser.ts +749 -62
- package/src/Markdown/MarkdownToGreenNode.ts +148 -27
- package/src/MilHibri/MilHibriDocumentModel.ts +56 -0
- package/src/Model/DocumentModel.ts +138 -5
- package/src/Model/TagRegistry.ts +129 -134
- package/src/Reconciler/SurgicalReconciler.ts +460 -0
- package/src/Repair/NestingRepair.ts +116 -0
- package/src/Repair/index.ts +1 -0
- package/src/Semantic/SemanticAnalyzer.ts +1845 -65
- package/src/Syntax/GreenNodePool.ts +12 -12
- package/src/Syntax/RedNode.ts +17 -3
- package/src/Syntax/RedNodeStore.ts +9 -1
- package/src/Syntax/nodeAttr.ts +172 -0
- package/src/Tokens/TokenResolver.ts +55 -0
- package/src/Tokens/index.ts +1 -0
- package/src/Types/core.ts +15 -0
- package/src/Utils/ColorMath.ts +276 -55
- package/src/Utils/EffectMath.ts +2462 -0
- package/src/Utils/TagVocabulary.ts +141 -0
- package/src/Utils/dom-to-svg.ts +28 -12
- package/src/Visitors/BBBlocksExporter.ts +15 -1
- package/src/Visitors/BBCodeExporter.ts +365 -32
- package/src/Visitors/BlockPatcher.ts +123 -22
- package/src/Visitors/DOMMorpher.ts +44 -28
- package/src/Visitors/HTMLRenderer.ts +771 -205
- package/src/Visitors/MarkdownExporter.ts +98 -41
- package/src/Visitors/TiptapExporter.ts +1 -0
- package/src/Visitors/index.ts +1 -1
- package/src/Visuals/BoxDrawer.ts +56 -10
- package/src/Visuals/LyneAudio.ts +346 -0
- package/src/Visuals/index.ts +8 -4
- package/src/Visuals/lyne.css +374 -9
- package/src/Visuals/osu.css +393 -11
- package/src/index.ts +178 -8
- package/dist/index.d.mts +0 -4612
- package/dist/index.d.ts +0 -4612
- package/src/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.ts +0 -135
- package/src/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.ts +0 -84
- package/src/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.ts +0 -99
- package/src/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.ts +0 -119
- package/src/Analysis/Passes/Decision/__tests__/DefaultDecision.test.ts +0 -179
- package/src/Analysis/__tests__/Integration.test.ts +0 -162
- package/src/Analysis/__tests__/Pipeline.test.ts +0 -133
- package/src/Diff/__tests__/TreeDiffer.test.ts +0 -65
- package/src/Incremental/__tests__/IncrementalParser.test.ts +0 -164
- package/src/Syntax/__tests__/GreenNode.test.ts +0 -33
- package/src/Syntax/__tests__/RedNode.test.ts +0 -81
- package/src/Syntax/__tests__/RedNodeStore.test.ts +0 -104
- package/src/Tests/ASTOptimizerIdempotence.test.ts +0 -77
- package/src/Tests/BlockPatcher.test.ts +0 -437
- package/src/Tests/BlockPatcherWindowed.test.ts +0 -364
- package/src/Tests/BoxDrawer.test.ts +0 -217
- package/src/Tests/BoxRichTitle.test.ts +0 -105
- package/src/Tests/Chars500kBenchmark.test.ts +0 -151
- package/src/Tests/Chars500kEdits.test.ts +0 -321
- package/src/Tests/CollabPositions.test.ts +0 -146
- package/src/Tests/CompilerPathProfiling.test.ts +0 -186
- package/src/Tests/DOMMorpher.test.ts +0 -142
- package/src/Tests/DomPatchPerf.test.ts +0 -60
- package/src/Tests/EffectSegments.snapshot.json +0 -616
- package/src/Tests/EffectSegments.test.ts +0 -68
- package/src/Tests/FindNodeAtOffset.test.ts +0 -65
- package/src/Tests/Fuzzer.test.ts +0 -166
- package/src/Tests/GreenNodePool.test.ts +0 -153
- package/src/Tests/Lexer.test.ts +0 -238
- package/src/Tests/LyneMode.test.ts +0 -291
- package/src/Tests/ModelCoherence.test.ts +0 -180
- package/src/Tests/Partition.test.ts +0 -238
- package/src/Tests/PluginTags.test.ts +0 -150
- package/src/Tests/ProblematicSection.test.ts +0 -46
- package/src/Tests/ProblematicSectionHTML.test.ts +0 -58
- package/src/Tests/RedReuse.test.ts +0 -134
- package/src/Tests/ReproDelete20k.test.ts +0 -62
- package/src/Tests/SemanticValidators.test.ts +0 -136
- package/src/Tests/StableNodeIds.test.ts +0 -210
- package/src/Tests/StudioColorBloat.test.ts +0 -25
- package/src/Tests/StudioDebugText.test.ts +0 -27
- package/src/Tests/StudioTrailingChar.test.ts +0 -25
- package/src/Tests/StudioValidText.test.ts +0 -25
- package/src/Tests/UrlImgBug.test.ts +0 -23
- package/src/Tests/VisualBuilderFidelity.test.ts +0 -105
- package/src/Tests/referenceDocument.ts +0 -119
- package/src/Utils/dom-to-svg.test.ts +0 -86
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quasar — `reorder-wrappers` (opt-in)
|
|
3
|
+
*
|
|
4
|
+
* Puts a chain of single-child wrappers into a canonical order:
|
|
5
|
+
*
|
|
6
|
+
* [color=#F00][b]x[/b][/color] → [b][color=#F00]x[/color][/b]
|
|
7
|
+
*
|
|
8
|
+
* ## It saves no bytes, and that is why it is not in the default preset
|
|
9
|
+
*
|
|
10
|
+
* Reordering removes nothing. Its value is canonical form — two documents that
|
|
11
|
+
* differ only in wrapper order become byte-identical, which is what lets other
|
|
12
|
+
* rules recognise runs they would otherwise miss. On its own, in a *minifier*,
|
|
13
|
+
* it produces a diff across someone's document in exchange for zero
|
|
14
|
+
* characters, so `defaultRules()` leaves it out and callers opt in.
|
|
15
|
+
*
|
|
16
|
+
* ## Byte-swaps, never regeneration
|
|
17
|
+
*
|
|
18
|
+
* The delimiters are moved, not rebuilt: each slot receives the exact source
|
|
19
|
+
* bytes of the delimiter that belongs there. `[Color = "#F00"]` comes out the
|
|
20
|
+
* other side character for character, which a tag-rebuilding implementation
|
|
21
|
+
* could not promise.
|
|
22
|
+
*
|
|
23
|
+
* ## Maximal
|
|
24
|
+
*
|
|
25
|
+
* The whole chain is sorted at once. Swapping one adjacent pair at a time
|
|
26
|
+
* would need repeated passes over offsets that no longer exist, so
|
|
27
|
+
* `[color][b][i]x[/i][/b][/color]` is permuted in a single step. The sort is
|
|
28
|
+
* stable, so equal-rank wrappers keep the order the author wrote.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import type { PlannedEdit } from '../EditPlan'
|
|
32
|
+
import {
|
|
33
|
+
type OptimizationRule,
|
|
34
|
+
type RuleContext,
|
|
35
|
+
type Positioned,
|
|
36
|
+
positionedChildren,
|
|
37
|
+
openRange,
|
|
38
|
+
closeRange,
|
|
39
|
+
hasBothDelimiters,
|
|
40
|
+
} from './Rule'
|
|
41
|
+
|
|
42
|
+
export const REORDER_WRAPPERS_PRIORITY = 5
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Nesting rank — lower belongs further out.
|
|
46
|
+
*
|
|
47
|
+
* Links outermost (a link wrapping styled text is the common authoring
|
|
48
|
+
* intent), then metrics, then weight/decoration, then colour innermost. Kinds
|
|
49
|
+
* absent from the table are not reordered at all: an unranked tag may carry
|
|
50
|
+
* layout or semantics that nesting order changes.
|
|
51
|
+
*/
|
|
52
|
+
const RANK: Readonly<Record<string, number>> = {
|
|
53
|
+
url: 1,
|
|
54
|
+
email: 1,
|
|
55
|
+
profile: 1,
|
|
56
|
+
font_size: 2,
|
|
57
|
+
font: 2,
|
|
58
|
+
bold: 3,
|
|
59
|
+
italic: 3,
|
|
60
|
+
underline: 3,
|
|
61
|
+
strikethrough: 3,
|
|
62
|
+
color: 4,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export class ReorderWrappersRule implements OptimizationRule {
|
|
66
|
+
readonly id = 'reorder-wrappers'
|
|
67
|
+
readonly priority = REORDER_WRAPPERS_PRIORITY
|
|
68
|
+
readonly label = 'Canonicalise nested wrapper order'
|
|
69
|
+
|
|
70
|
+
run(context: RuleContext): PlannedEdit[] {
|
|
71
|
+
const sink: PlannedEdit[] = []
|
|
72
|
+
this.scan({ node: context.root, start: 0 }, context.source, sink)
|
|
73
|
+
sink.sort((a, b) => a.start - b.start || a.end - b.end)
|
|
74
|
+
return sink
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── Private ─────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
private scan(item: Positioned, source: string, sink: PlannedEdit[]): void {
|
|
80
|
+
for (const child of positionedChildren(item)) {
|
|
81
|
+
const chain = this.chainFrom(child)
|
|
82
|
+
|
|
83
|
+
if (chain.length >= 2) {
|
|
84
|
+
this.emitPermutation(chain, source, sink)
|
|
85
|
+
// Continue below the chain. `scan` iterates its argument's children,
|
|
86
|
+
// so the innermost wrapper is what gets handed down — passing its
|
|
87
|
+
// children instead would skip a level.
|
|
88
|
+
this.scan(chain[chain.length - 1], source, sink)
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.scan(child, source, sink)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The maximal run of rankable single-child wrappers starting at `item`. */
|
|
97
|
+
private chainFrom(item: Positioned): Positioned[] {
|
|
98
|
+
const chain: Positioned[] = []
|
|
99
|
+
let current: Positioned | undefined = item
|
|
100
|
+
|
|
101
|
+
while (
|
|
102
|
+
current &&
|
|
103
|
+
RANK[current.node.kind] !== undefined &&
|
|
104
|
+
hasBothDelimiters(current) &&
|
|
105
|
+
current.node.children.length === 1
|
|
106
|
+
) {
|
|
107
|
+
chain.push(current)
|
|
108
|
+
const [only] = positionedChildren(current)
|
|
109
|
+
current = only
|
|
110
|
+
}
|
|
111
|
+
return chain
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private emitPermutation(
|
|
115
|
+
chain: readonly Positioned[],
|
|
116
|
+
source: string,
|
|
117
|
+
sink: PlannedEdit[],
|
|
118
|
+
): void {
|
|
119
|
+
const order = chain.map((_, index) => index)
|
|
120
|
+
// Stable by construction: ties fall back to the original index.
|
|
121
|
+
order.sort((a, b) => RANK[chain[a].node.kind] - RANK[chain[b].node.kind] || a - b)
|
|
122
|
+
|
|
123
|
+
if (order.every((sourceIndex, slot) => sourceIndex === slot)) return
|
|
124
|
+
|
|
125
|
+
const label = `Canonicalise ${chain.map(c => `[${c.node.kind}]`).join('')}`
|
|
126
|
+
|
|
127
|
+
for (let slot = 0; slot < chain.length; slot++) {
|
|
128
|
+
const from = chain[order[slot]]
|
|
129
|
+
if (order[slot] === slot) continue
|
|
130
|
+
|
|
131
|
+
const openSlot = openRange(chain[slot])
|
|
132
|
+
const closeSlot = closeRange(chain[slot])
|
|
133
|
+
const openFrom = openRange(from)
|
|
134
|
+
const closeFrom = closeRange(from)
|
|
135
|
+
|
|
136
|
+
sink.push({
|
|
137
|
+
start: openSlot.start,
|
|
138
|
+
end: openSlot.end,
|
|
139
|
+
text: source.slice(openFrom.start, openFrom.end),
|
|
140
|
+
ruleId: this.id,
|
|
141
|
+
priority: this.priority,
|
|
142
|
+
label,
|
|
143
|
+
})
|
|
144
|
+
sink.push({
|
|
145
|
+
start: closeSlot.start,
|
|
146
|
+
end: closeSlot.end,
|
|
147
|
+
text: source.slice(closeFrom.start, closeFrom.end),
|
|
148
|
+
ruleId: this.id,
|
|
149
|
+
priority: this.priority,
|
|
150
|
+
label,
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quasar — `shorten-hex`
|
|
3
|
+
*
|
|
4
|
+
* `[color=#FFAA00]` → `[color=#FA0]`, three characters per tag.
|
|
5
|
+
*
|
|
6
|
+
* Only fires when every channel is a doubled digit, which is exactly the set
|
|
7
|
+
* of six-digit colours the three-digit form can express — `#FA0` expands back
|
|
8
|
+
* to `#FFAA00` by duplication, so the rendered colour is bit-identical. This
|
|
9
|
+
* was verified against the engine before the rule was written: three-digit hex
|
|
10
|
+
* parses to a `color` node and reaches CSS as `color:#FA0`.
|
|
11
|
+
*
|
|
12
|
+
* The edit replaces the hex token alone, located inside the opening
|
|
13
|
+
* delimiter — never the delimiter itself. `[Color = "#FFAA00"]` therefore
|
|
14
|
+
* becomes `[Color = "#FA0"]`, keeping the capital C, the spaces and the
|
|
15
|
+
* quotes the author typed.
|
|
16
|
+
*
|
|
17
|
+
* Three bytes sounds trivial and is not: an expanded gradient spends one
|
|
18
|
+
* colour tag per character, so a 400-character ramp gives back 1.200.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { PlannedEdit } from '../EditPlan'
|
|
22
|
+
import {
|
|
23
|
+
type OptimizationRule,
|
|
24
|
+
type RuleContext,
|
|
25
|
+
type Positioned,
|
|
26
|
+
positionedChildren,
|
|
27
|
+
openRange,
|
|
28
|
+
} from './Rule'
|
|
29
|
+
import { attributeValue, shortenableHex } from './tagValue'
|
|
30
|
+
|
|
31
|
+
export const SHORTEN_HEX_PRIORITY = 10
|
|
32
|
+
|
|
33
|
+
const HEX6_TOKEN = /#[0-9a-fA-F]{6}/
|
|
34
|
+
|
|
35
|
+
export class ShortenHexRule implements OptimizationRule {
|
|
36
|
+
readonly id = 'shorten-hex'
|
|
37
|
+
readonly priority = SHORTEN_HEX_PRIORITY
|
|
38
|
+
readonly label = 'Shorten 6-digit hex colours'
|
|
39
|
+
|
|
40
|
+
run(context: RuleContext): PlannedEdit[] {
|
|
41
|
+
const sink: PlannedEdit[] = []
|
|
42
|
+
this.scan({ node: context.root, start: 0 }, context.source, sink)
|
|
43
|
+
return sink
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Private ─────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
private scan(item: Positioned, source: string, sink: PlannedEdit[]): void {
|
|
49
|
+
for (const child of positionedChildren(item)) {
|
|
50
|
+
if (child.node.kind === 'color') {
|
|
51
|
+
const edit = this.shorten(child, source)
|
|
52
|
+
if (edit) sink.push(edit)
|
|
53
|
+
}
|
|
54
|
+
this.scan(child, source, sink)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private shorten(item: Positioned, source: string): PlannedEdit | null {
|
|
59
|
+
const short = shortenableHex(attributeValue(item.node))
|
|
60
|
+
if (short === null) return null
|
|
61
|
+
|
|
62
|
+
const open = openRange(item)
|
|
63
|
+
const delimiter = source.slice(open.start, open.end)
|
|
64
|
+
const match = HEX6_TOKEN.exec(delimiter)
|
|
65
|
+
// The attribute said there was a six-digit hex; if it is not visible in the
|
|
66
|
+
// delimiter the two disagree, and guessing an offset would corrupt bytes.
|
|
67
|
+
if (!match) return null
|
|
68
|
+
|
|
69
|
+
const start = open.start + match.index
|
|
70
|
+
return {
|
|
71
|
+
start,
|
|
72
|
+
end: start + match[0].length,
|
|
73
|
+
text: short,
|
|
74
|
+
ruleId: this.id,
|
|
75
|
+
priority: this.priority,
|
|
76
|
+
label: `${match[0]} → ${short}`,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quasar — Tag attribute reading & normalisation for optimization rules
|
|
3
|
+
*
|
|
4
|
+
* Rules compare tags by what they *mean*, not by how they are spelled:
|
|
5
|
+
* `[Color = "#FF0000"]`, `[color=#ff0000]` and `[color=#F00]` all name the
|
|
6
|
+
* same colour and must be treated as one. Normalising here is what lets the
|
|
7
|
+
* merge rule fuse them while still leaving the author's own bytes in place.
|
|
8
|
+
*
|
|
9
|
+
* `Analysis/Utils/color-utils.extractHex` is deliberately stricter — it
|
|
10
|
+
* accepts only six-digit hex, because a *recolouring* pass must not rewrite a
|
|
11
|
+
* spelling the author chose. These rules have the opposite need: they only
|
|
12
|
+
* ever delete or shorten, so recognising more spellings is pure upside.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { GreenNode } from '../../Syntax/GreenNode'
|
|
16
|
+
|
|
17
|
+
const QUOTES = new Set(['"', "'"])
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The attribute value a tag node carries, unquoted and trimmed.
|
|
21
|
+
*
|
|
22
|
+
* A node's `text` holds the raw attribute segment of its opening delimiter —
|
|
23
|
+
* `=#FF0000`, `= "#FF0000"`, or `''` for a bare tag.
|
|
24
|
+
*/
|
|
25
|
+
export function attributeValue(node: GreenNode): string {
|
|
26
|
+
const text = node.text || ''
|
|
27
|
+
const eq = text.indexOf('=')
|
|
28
|
+
let value = (eq >= 0 ? text.slice(eq + 1) : text).trim()
|
|
29
|
+
|
|
30
|
+
if (value.length >= 2 && QUOTES.has(value[0]) && value[value.length - 1] === value[0]) {
|
|
31
|
+
value = value.slice(1, -1).trim()
|
|
32
|
+
}
|
|
33
|
+
return value
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const HEX6 = /^#[0-9a-fA-F]{6}$/
|
|
37
|
+
const HEX3 = /^#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Canonical form of a colour value, for equality only.
|
|
41
|
+
*
|
|
42
|
+
* Never written back to the document — `#F00` and `#FF0000` normalise to the
|
|
43
|
+
* same string so the two tags can merge, but whichever one the author wrote is
|
|
44
|
+
* the one that survives.
|
|
45
|
+
*/
|
|
46
|
+
export function normalizeColorValue(raw: string): string {
|
|
47
|
+
const short = HEX3.exec(raw)
|
|
48
|
+
if (short) return `#${short[1]}${short[1]}${short[2]}${short[2]}${short[3]}${short[3]}`.toUpperCase()
|
|
49
|
+
if (HEX6.test(raw)) return raw.toUpperCase()
|
|
50
|
+
// Keywords (`red`, `Transparent`) are case-insensitive in CSS.
|
|
51
|
+
return raw.toLowerCase()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Six-digit hex whose channels are all doubled digits, e.g. `#FFAA00`. */
|
|
55
|
+
export function shortenableHex(raw: string): string | null {
|
|
56
|
+
if (!HEX6.test(raw)) return null
|
|
57
|
+
const r = raw.slice(1)
|
|
58
|
+
if (r[0].toLowerCase() !== r[1].toLowerCase()) return null
|
|
59
|
+
if (r[2].toLowerCase() !== r[3].toLowerCase()) return null
|
|
60
|
+
if (r[4].toLowerCase() !== r[5].toLowerCase()) return null
|
|
61
|
+
// Keep the author's own digit casing; only the redundant half is dropped.
|
|
62
|
+
return `#${r[0]}${r[2]}${r[4]}`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The identity two sibling tags must share to be mergeable.
|
|
67
|
+
*
|
|
68
|
+
* Returns `null` for kinds this optimizer does not fuse.
|
|
69
|
+
*/
|
|
70
|
+
export function mergeIdentity(node: GreenNode): string | null {
|
|
71
|
+
if (node.kind === 'color') return `color|${normalizeColorValue(attributeValue(node))}`
|
|
72
|
+
if (MERGEABLE_INLINE.has(node.kind)) return `${node.kind}|${attributeValue(node).toLowerCase()}`
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Inline formatting tags that fuse when adjacent and identically attributed.
|
|
78
|
+
*
|
|
79
|
+
* Block kinds are absent on purpose. Merging `[box]a[/box][box]b[/box]` into
|
|
80
|
+
* one box is not a rewrite of the same document — it renders one frame where
|
|
81
|
+
* the author drew two.
|
|
82
|
+
*/
|
|
83
|
+
export const MERGEABLE_INLINE: ReadonlySet<string> = new Set([
|
|
84
|
+
'bold',
|
|
85
|
+
'italic',
|
|
86
|
+
'underline',
|
|
87
|
+
'strikethrough',
|
|
88
|
+
'font_size',
|
|
89
|
+
'font',
|
|
90
|
+
'sup',
|
|
91
|
+
'sub',
|
|
92
|
+
'mark',
|
|
93
|
+
])
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Kinds that may absorb the whitespace sitting between two merged siblings.
|
|
97
|
+
*
|
|
98
|
+
* `[color=X]a[/color] [color=X]b[/color]` → `[color=X]a b[/color]` is safe: a
|
|
99
|
+
* coloured space and an uncoloured space are the same pixels. The same move on
|
|
100
|
+
* `[u]` is **not** — the underline would extend across the gap, which is a
|
|
101
|
+
* visible change to a document the user only asked to make smaller. Size and
|
|
102
|
+
* font are excluded for the same reason: a space in another size or face can
|
|
103
|
+
* change advance width and line height.
|
|
104
|
+
*/
|
|
105
|
+
export const BRIDGES_WHITESPACE: ReadonlySet<string> = new Set(['color', 'bold', 'italic'])
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quasar — `unwrap-invisible-color`
|
|
3
|
+
*
|
|
4
|
+
* Removes a `[color]` whose entire content is whitespace.
|
|
5
|
+
*
|
|
6
|
+
* [color=#6A4C93]BARCA[/color][color=#6F518D] [/color][color=#755687]·[/color]
|
|
7
|
+
* ╰── 24 bytes to colour a space ──╯
|
|
8
|
+
*
|
|
9
|
+
* A space has no glyph, and `[color]` sets `color` and nothing else — no
|
|
10
|
+
* background, no decoration — so painting one is a guaranteed no-op. Deleting
|
|
11
|
+
* the two delimiters turns 24 bytes into 1 and cannot change a pixel.
|
|
12
|
+
*
|
|
13
|
+
* This is the dominant waste in expanded gradients, and it is easy to see why:
|
|
14
|
+
* the tool that expands `[gradient]` into per-character `[color]` spans walks
|
|
15
|
+
* characters without asking which of them draw ink, so every space in the ramp
|
|
16
|
+
* gets its own fully-spelled tag.
|
|
17
|
+
*
|
|
18
|
+
* ## Only colour
|
|
19
|
+
*
|
|
20
|
+
* The same shape is *not* removable for other tags, and the difference is not
|
|
21
|
+
* subtle:
|
|
22
|
+
*
|
|
23
|
+
* - `[u]` / `[s]` draw a line through the gap — visible on a space.
|
|
24
|
+
* - `[mark]` paints a background — visible on a space.
|
|
25
|
+
* - `[size]` and `[font]` change the advance width of the space itself.
|
|
26
|
+
*
|
|
27
|
+
* Only `color` is invisible on whitespace, so only `color` is unwrapped.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import type { GreenNode } from '../../Syntax/GreenNode'
|
|
31
|
+
import type { PlannedEdit } from '../EditPlan'
|
|
32
|
+
import {
|
|
33
|
+
type OptimizationRule,
|
|
34
|
+
type RuleContext,
|
|
35
|
+
type Positioned,
|
|
36
|
+
positionedChildren,
|
|
37
|
+
openRange,
|
|
38
|
+
closeRange,
|
|
39
|
+
hasBothDelimiters,
|
|
40
|
+
deletion,
|
|
41
|
+
} from './Rule'
|
|
42
|
+
|
|
43
|
+
export const UNWRAP_INVISIBLE_COLOR_PRIORITY = 85
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Whether a node contributes nothing but whitespace.
|
|
47
|
+
*
|
|
48
|
+
* Recursive through nested colours, so `[color=A][color=B] [/color][/color]`
|
|
49
|
+
* counts. `spacing` is whitespace too — a newline inside a colour tag is as
|
|
50
|
+
* invisible as a space.
|
|
51
|
+
*/
|
|
52
|
+
export function isInvisibleWhitespace(node: GreenNode): boolean {
|
|
53
|
+
if (node.kind === 'text') return /^\s*$/.test(node.text ?? '')
|
|
54
|
+
if (node.kind === 'spacing' || node.kind === 'empty_line') return true
|
|
55
|
+
if (node.kind !== 'color') return false
|
|
56
|
+
|
|
57
|
+
const children = node.children as readonly GreenNode[]
|
|
58
|
+
if (children.length === 0) return node.width - node.leadingWidth - node.trailingWidth === 0
|
|
59
|
+
return children.every(isInvisibleWhitespace)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export class UnwrapInvisibleColorRule implements OptimizationRule {
|
|
63
|
+
readonly id = 'unwrap-invisible-color'
|
|
64
|
+
readonly priority = UNWRAP_INVISIBLE_COLOR_PRIORITY
|
|
65
|
+
readonly label = 'Unwrap colour tags around whitespace'
|
|
66
|
+
|
|
67
|
+
run(context: RuleContext): PlannedEdit[] {
|
|
68
|
+
const sink: PlannedEdit[] = []
|
|
69
|
+
this.scan({ node: context.root, start: 0 }, sink)
|
|
70
|
+
sink.sort((a, b) => a.start - b.start || a.end - b.end)
|
|
71
|
+
return sink
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Private ─────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
private scan(item: Positioned, sink: PlannedEdit[]): void {
|
|
77
|
+
for (const child of positionedChildren(item)) {
|
|
78
|
+
if (
|
|
79
|
+
child.node.kind === 'color' &&
|
|
80
|
+
hasBothDelimiters(child) &&
|
|
81
|
+
isInvisibleWhitespace(child.node)
|
|
82
|
+
) {
|
|
83
|
+
const label = 'Unwrap colour around whitespace'
|
|
84
|
+
sink.push(deletion(openRange(child), this.id, this.priority, label))
|
|
85
|
+
sink.push(deletion(closeRange(child), this.id, this.priority, label))
|
|
86
|
+
// Nested colours inside are invisible too; keep unwrapping them.
|
|
87
|
+
this.scan(child, sink)
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
90
|
+
this.scan(child, sink)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quasar — applying a resolved edit set to source text
|
|
3
|
+
*
|
|
4
|
+
* The string-rewriting half of the two appliers. Monaco is the other: it takes
|
|
5
|
+
* the very same `SurgicalEdit[]` and resolves the offsets against its model
|
|
6
|
+
* itself, which is why neither side ever re-serializes the document and the
|
|
7
|
+
* two cannot drift.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { SurgicalEdit } from '../Reconciler/SurgicalReconciler'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Apply edits carrying ORIGINAL offsets, in one pass.
|
|
14
|
+
*
|
|
15
|
+
* The obvious implementation splices the string once per edit:
|
|
16
|
+
*
|
|
17
|
+
* for (const edit of backToFront) out = out.slice(0, s) + text + out.slice(e)
|
|
18
|
+
*
|
|
19
|
+
* which is correct and quadratic — every splice copies the whole document, so
|
|
20
|
+
* a 250.000-character page with ~3.800 edits copies roughly a billion
|
|
21
|
+
* characters and takes about half a second. Measured, not guessed. Collecting
|
|
22
|
+
* the untouched spans and joining them once is linear and turns that into a
|
|
23
|
+
* few milliseconds, which is the difference between a minify command that
|
|
24
|
+
* feels instant and one that visibly stalls the editor.
|
|
25
|
+
*
|
|
26
|
+
* Going forwards rather than backwards is what makes the single pass possible,
|
|
27
|
+
* and it costs nothing: each edit is measured against the original string
|
|
28
|
+
* either way.
|
|
29
|
+
*
|
|
30
|
+
* ## Contract
|
|
31
|
+
*
|
|
32
|
+
* The edits must be pairwise conflict-free — run them through
|
|
33
|
+
* `resolveEditConflicts` first. An edit that reaches back into a span already
|
|
34
|
+
* written is a contract violation with no meaningful answer, so it is skipped
|
|
35
|
+
* rather than allowed to interleave into nonsense; the result stays a valid
|
|
36
|
+
* document that is merely missing one change.
|
|
37
|
+
*/
|
|
38
|
+
export function applyEditsToSource(source: string, edits: readonly SurgicalEdit[]): string {
|
|
39
|
+
if (edits.length === 0) return source
|
|
40
|
+
|
|
41
|
+
const ordered = [...edits].sort((a, b) => a.start - b.start || a.end - b.end)
|
|
42
|
+
const segments: string[] = []
|
|
43
|
+
let cursor = 0
|
|
44
|
+
|
|
45
|
+
for (const edit of ordered) {
|
|
46
|
+
if (edit.start < cursor) continue
|
|
47
|
+
if (edit.start > cursor) segments.push(source.slice(cursor, edit.start))
|
|
48
|
+
if (edit.text) segments.push(edit.text)
|
|
49
|
+
cursor = edit.end
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (cursor < source.length) segments.push(source.slice(cursor))
|
|
53
|
+
return segments.join('')
|
|
54
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export {
|
|
2
|
+
classifyOverlap,
|
|
3
|
+
editsConflict,
|
|
4
|
+
compareEditPriority,
|
|
5
|
+
resolveEditConflicts,
|
|
6
|
+
} from './EditPlan'
|
|
7
|
+
export type {
|
|
8
|
+
PlannedEdit,
|
|
9
|
+
RejectedEdit,
|
|
10
|
+
ResolvedEditPlan,
|
|
11
|
+
EditRejectionReason,
|
|
12
|
+
OverlapRelation,
|
|
13
|
+
} from './EditPlan'
|
|
14
|
+
export { applyEditsToSource } from './applyEdits'
|
|
15
|
+
export {
|
|
16
|
+
optimizeBBCode,
|
|
17
|
+
optimizeTree,
|
|
18
|
+
defaultRules,
|
|
19
|
+
allRules,
|
|
20
|
+
} from './Optimizer'
|
|
21
|
+
export type { OptimizationResult, OptimizeOptions, RuleStat } from './Optimizer'
|
|
22
|
+
export * from './Rules'
|
package/src/Events/EventBus.ts
CHANGED
|
@@ -45,6 +45,24 @@ export interface DocumentEvent {
|
|
|
45
45
|
* of changes it applied itself. Absent on events with no single cause.
|
|
46
46
|
*/
|
|
47
47
|
origin?: string
|
|
48
|
+
/**
|
|
49
|
+
* On `diagnostics_updated`: how the analysis that produced them was
|
|
50
|
+
* obtained — `'full'` (every node revalidated) or `'window'` (only the
|
|
51
|
+
* nodes the edit could have changed; every other node kept its verdict).
|
|
52
|
+
*
|
|
53
|
+
* The `diagnostics` on the event are the WHOLE document's either way, so a
|
|
54
|
+
* subscriber renders them the same in both cases. This is here for the
|
|
55
|
+
* cases where the difference is the point: a performance overlay, a test
|
|
56
|
+
* that must notice the incremental path going away, a log line explaining a
|
|
57
|
+
* slow frame. See `AnalyzeScope`.
|
|
58
|
+
*/
|
|
59
|
+
analysisScope?: 'full' | 'window'
|
|
60
|
+
/**
|
|
61
|
+
* On `diagnostics_updated` with `analysisScope === 'window'`: the span of
|
|
62
|
+
* the current source that was re-validated, in current coordinates.
|
|
63
|
+
* `null`/absent when the whole document was.
|
|
64
|
+
*/
|
|
65
|
+
analysisWindow?: { start: number; end: number } | null
|
|
48
66
|
timestamp: number
|
|
49
67
|
[key: string]: unknown
|
|
50
68
|
}
|