Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Region-based semantic diagnostics #57842

Merged
merged 83 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
ce6ed79
WIP
gabritto Feb 27, 2024
33ed16a
add nodesToCheck param; compute it from ranges
gabritto Mar 1, 2024
cf54694
more end-to-end changes
gabritto Mar 2, 2024
9647997
lint fixes, api baseline changes
gabritto Mar 2, 2024
9cd0edc
small fix, update baseline
gabritto Mar 4, 2024
eeb91c5
get project right before checking for errors
gabritto Mar 4, 2024
38861fb
fix type error and tests
gabritto Mar 5, 2024
b46560a
WIP: return spans in region diagnostics event
gabritto Mar 7, 2024
22637a4
add perf for testing
gabritto Mar 8, 2024
6e03d01
include less nodes in nodes to check
gabritto Mar 12, 2024
7af55c2
add unit test, more fixes
gabritto Mar 15, 2024
1d2b665
fix things and add unit test
gabritto Mar 15, 2024
e0cf3c6
Merge branch 'main' into gabritto/regiondiag
gabritto Mar 19, 2024
c25a879
refactors
gabritto Mar 19, 2024
0a55946
refactor type
gabritto Mar 19, 2024
1b4e458
update protocol comment
gabritto Mar 22, 2024
a936ff0
restore checker arrays between region and full check
gabritto Mar 26, 2024
13543e1
fmt
gabritto Mar 26, 2024
7b53e4f
only include unused directive errors for checked range
gabritto Mar 28, 2024
69aacf4
properly compute checked span
gabritto Mar 28, 2024
63c543d
update comments
gabritto Mar 28, 2024
b51921d
pass around compressed spans so that we do unused directive check mor…
gabritto Mar 28, 2024
aac18b1
Revert "pass around compressed spans so that we do unused directive c…
gabritto Mar 31, 2024
9a1c344
don't error on unused directives
gabritto Mar 31, 2024
64d0699
tweak node selection
gabritto Apr 1, 2024
d445be5
add region semantic test infra; inconsistent diagnostic test
gabritto Apr 1, 2024
43712cd
Merge branch 'main' into gabritto/regiondiag
gabritto Apr 1, 2024
ab5a25c
update test baseline
gabritto Apr 1, 2024
148f9ad
add inconsistent 2307 test
gabritto Apr 2, 2024
58a4f5f
2354 inconsistent position test
gabritto Apr 2, 2024
1ac37e8
test for extra 2344
gabritto Apr 2, 2024
9276bd3
add test for spans checked by region check
gabritto Apr 3, 2024
caf6fc4
add test for extra 2416
gabritto Apr 3, 2024
e102e49
add test for 2740 extra
gabritto Apr 3, 2024
32ec3b1
add test for react error
gabritto Apr 4, 2024
52b9e33
avoid re-checking nodes
gabritto Apr 10, 2024
80698d3
format
gabritto Apr 10, 2024
467b1a2
Merge branch 'main' into gabritto/regiondiag
gabritto Apr 25, 2024
ae6a7ec
remove console.log
gabritto Apr 26, 2024
0a47d91
Merge branch 'main' into gabritto/regiondiag
gabritto Apr 29, 2024
46813c7
update baseline
gabritto Apr 29, 2024
dee2885
remove failing tests
gabritto May 3, 2024
898325f
Merge branch 'main' into gabritto/regiondiag
gabritto May 3, 2024
13a7ab7
fmt
gabritto May 3, 2024
7e8d763
fix default for var
gabritto May 3, 2024
b51af9c
CR: get rid of unnecessary boolean flag for partial check
gabritto May 21, 2024
a3cdc74
CR: get rid of option to exclude duration from diagnostics event, san…
gabritto May 21, 2024
4d3f068
CR: make `shouldDoRegionCheck` internal and override it in `TestSession`
gabritto May 21, 2024
11b7966
CR: clean up session's semantic check perf data when file is closed
gabritto May 21, 2024
f02cef4
CR: make unit test helper `verifyGetErrRequest` support region-based …
gabritto May 21, 2024
e62ec2e
CR: mark region diagnostics methods as internal
gabritto May 21, 2024
90ef005
CR: add test for multiple files; fix testing helper to support region…
gabritto May 22, 2024
eaa9b53
fix overload lint error
gabritto May 22, 2024
3e49b6b
CR: add unit tests with suggestions
gabritto May 22, 2024
0ef9bb8
change order of checking for files with specified regions
gabritto May 22, 2024
8e160cf
fix log sanitizing regex
gabritto May 22, 2024
30a0aad
Merge branch 'main' into gabritto/regiondiag
gabritto May 22, 2024
ece3c41
update unit tests logs with duration
gabritto May 23, 2024
0251194
remove unused baseline
gabritto May 23, 2024
7076d14
optimization: don't do region node selection if we will skip checking…
gabritto May 23, 2024
48f87a8
don't record semantic diagnostic time if could have done region check
gabritto May 23, 2024
81dc370
improve optimization
gabritto May 23, 2024
5e6ce98
add line count and use it for deciding on region check
gabritto May 24, 2024
3254be9
TEMPORARY FOR TESTING VSCODE
gabritto May 30, 2024
ef6e4d5
get rid of performance checking in heuristic for now
gabritto May 30, 2024
c07b226
CR: mark internal
gabritto May 30, 2024
1f262cd
CR: refactor session.ts
gabritto May 30, 2024
34bcbb2
always do semantic check in next immediate step
gabritto Jun 7, 2024
52eadaa
fix rebase problem
gabritto Jun 7, 2024
12979b7
fix baselines
gabritto Jun 10, 2024
830c118
check files in the order of the request
gabritto Jun 10, 2024
d3a8f41
refactor unit test
gabritto Jun 11, 2024
1752f8f
add option for test session to always enable region checking
gabritto Jun 11, 2024
9320a45
add test for skipped region diagnostics
gabritto Jun 11, 2024
68831d1
format
gabritto Jun 11, 2024
2f2e02f
Merge branch 'main' into gabritto/regiondiag
gabritto Jun 11, 2024
eb168b6
add test for file with ts-nocheck
gabritto Jun 11, 2024
c571ae0
Merge branch 'main' into gabritto/regiondiag
gabritto Jun 12, 2024
7c512ac
add comments and minor refactor
gabritto Jun 12, 2024
9f2727b
Merge branch 'main' into gabritto/regiondiag
gabritto Jun 13, 2024
ec6772c
refactor
gabritto Jun 13, 2024
f1d87dc
refactor updateErrorCheck to delay work again
gabritto Jun 13, 2024
5ed581a
mark region threshold as internal
gabritto Jun 13, 2024
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
50 changes: 36 additions & 14 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46838,6 +46838,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
errorOrSuggestion(compilerOptions.allowUnreachableCode === false, node, Diagnostics.Unreachable_code_detected);
}

// If editing this, keep `isSourceElement` in utilities up to date.
switch (kind) {
case SyntaxKind.TypeParameter:
return checkTypeParameter(node as TypeParameterDeclaration);
Expand Down Expand Up @@ -47200,12 +47201,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
tracing?.pop();
}

function checkSourceFile(node: SourceFile) {
tracing?.push(tracing.Phase.Check, "checkSourceFile", { path: node.path }, /*separateBeginAndEnd*/ true);
performance.mark("beforeCheck");
checkSourceFileWorker(node);
performance.mark("afterCheck");
performance.measure("Check", "beforeCheck", "afterCheck");
function checkSourceFile(node: SourceFile, nodesToCheck: Node[] | undefined) {
tracing?.push(tracing.Phase.Check, nodesToCheck ? "checkSourceFileNodes" : "checkSourceFile", { path: node.path }, /*separateBeginAndEnd*/ true);
const beforeMark = nodesToCheck ? "beforeCheckNodes" : "beforeCheck";
const afterMark = nodesToCheck ? "afterCheckNodes" : "afterCheck";
performance.mark(beforeMark);
nodesToCheck ? checkSourceFileNodesWorker(node, nodesToCheck) : checkSourceFileWorker(node);
performance.mark(afterMark);
performance.measure("Check", beforeMark, afterMark);
tracing?.pop();
}

Expand Down Expand Up @@ -47295,13 +47298,29 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}

function getDiagnostics(sourceFile: SourceFile, ct: CancellationToken): Diagnostic[] {
function checkSourceFileNodesWorker(file: SourceFile, nodes: readonly Node[]) {
const links = getNodeLinks(file);
if (!(links.flags & NodeCheckFlags.TypeChecked)) {
if (skipTypeChecking(file, compilerOptions, host)) {
return;
}

// Grammar checking
checkGrammarSourceFile(file);

forEach(nodes, checkSourceElement);

checkDeferredNodes(file);
}
}

function getDiagnostics(sourceFile: SourceFile, ct: CancellationToken, nodesToCheck?: Node[]): Diagnostic[] {
try {
// Record the cancellation token so it can be checked later on during checkSourceElement.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly I think this whole "one giant array kept until the end" is super weird and I've been wondering if we could refactor this to not use this pattern...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could make my life more convenient for this PR.

// Do this in a finally block so we can ensure that it gets reset back to nothing after
// this call is done.
cancellationToken = ct;
return getDiagnosticsWorker(sourceFile);
return getDiagnosticsWorker(sourceFile, nodesToCheck);
}
finally {
cancellationToken = undefined;
Expand All @@ -47316,7 +47335,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
deferredDiagnosticsCallbacks = [];
}

function checkSourceFileWithEagerDiagnostics(sourceFile: SourceFile) {
function checkSourceFileWithEagerDiagnostics(sourceFile: SourceFile, nodesToCheck?: Node[]) {
ensurePendingDiagnosticWorkComplete();
// then setup diagnostics for immediate invocation (as we are about to collect them, and
// this avoids the overhead of longer-lived callbacks we don't need to allocate)
Expand All @@ -47325,11 +47344,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// thus much more likely retaining the same union ordering as before we had lazy diagnostics)
const oldAddLazyDiagnostics = addLazyDiagnostic;
addLazyDiagnostic = cb => cb();
checkSourceFile(sourceFile);
checkSourceFile(sourceFile, nodesToCheck);
addLazyDiagnostic = oldAddLazyDiagnostics;
}

function getDiagnosticsWorker(sourceFile: SourceFile): Diagnostic[] {
function getDiagnosticsWorker(sourceFile: SourceFile, nodesToCheck: Node[] | undefined): Diagnostic[] {
if (sourceFile) {
ensurePendingDiagnosticWorkComplete();
// Some global diagnostics are deferred until they are needed and
Expand All @@ -47338,9 +47357,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const previousGlobalDiagnostics = diagnostics.getGlobalDiagnostics();
const previousGlobalDiagnosticsSize = previousGlobalDiagnostics.length;

checkSourceFileWithEagerDiagnostics(sourceFile);

checkSourceFileWithEagerDiagnostics(sourceFile, nodesToCheck);
const semanticDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName);
if (nodesToCheck) {
// No need to get global diagnostics.
return semanticDiagnostics;
}
const currentGlobalDiagnostics = diagnostics.getGlobalDiagnostics();
if (currentGlobalDiagnostics !== previousGlobalDiagnostics) {
// If the arrays are not the same reference, new diagnostics were added.
Expand All @@ -47359,7 +47381,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

// Global diagnostics are always added when a file is not provided to
// getDiagnostics
forEach(host.getSourceFiles(), checkSourceFileWithEagerDiagnostics);
forEach(host.getSourceFiles(), file => checkSourceFileWithEagerDiagnostics(file));
return diagnostics.getDiagnostics();
}

Expand Down
35 changes: 27 additions & 8 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2798,8 +2798,12 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
return getDiagnosticsHelper(sourceFile, getSyntacticDiagnosticsForFile, cancellationToken);
}

function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] {
return getDiagnosticsHelper(sourceFile, getSemanticDiagnosticsForFile, cancellationToken);
function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken, nodesToCheck?: Node[]): readonly Diagnostic[] {
return getDiagnosticsHelper(
sourceFile,
(sourceFile, cancellationToken) => getSemanticDiagnosticsForFile(sourceFile, cancellationToken, nodesToCheck),
cancellationToken,
);
}

function getCachedSemanticDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] | undefined {
Expand All @@ -2809,7 +2813,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
}

function getBindAndCheckDiagnostics(sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] {
return getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken);
return getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken, /*nodesToCheck*/ undefined);
}

function getProgramDiagnostics(sourceFile: SourceFile): readonly Diagnostic[] {
Expand Down Expand Up @@ -2863,18 +2867,33 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
}
}

function getSemanticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] {
function getSemanticDiagnosticsForFile(
sourceFile: SourceFile,
cancellationToken: CancellationToken | undefined,
nodesToCheck: Node[] | undefined,
): readonly Diagnostic[] {
return concatenate(
filterSemanticDiagnostics(getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken), options),
filterSemanticDiagnostics(getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken, nodesToCheck), options),
getProgramDiagnostics(sourceFile),
);
}

function getBindAndCheckDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] {
function getBindAndCheckDiagnosticsForFile(
sourceFile: SourceFile,
cancellationToken: CancellationToken | undefined,
nodesToCheck: Node[] | undefined,
): readonly Diagnostic[] {
if (nodesToCheck) {
return getBindAndCheckDiagnosticsForFileNoCache(sourceFile, cancellationToken, nodesToCheck);
}
return getAndCacheDiagnostics(sourceFile, cancellationToken, cachedBindAndCheckDiagnosticsForFile, getBindAndCheckDiagnosticsForFileNoCache);
}

function getBindAndCheckDiagnosticsForFileNoCache(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] {
function getBindAndCheckDiagnosticsForFileNoCache(
sourceFile: SourceFile,
cancellationToken: CancellationToken | undefined,
nodesToCheck?: Node[],
): readonly Diagnostic[] {
return runWithCancellationToken(() => {
if (skipTypeChecking(sourceFile, options, program)) {
return emptyArray;
Expand All @@ -2896,7 +2915,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
const includeBindAndCheckDiagnostics = !isTsNoCheck && (sourceFile.scriptKind === ScriptKind.TS || sourceFile.scriptKind === ScriptKind.TSX
|| sourceFile.scriptKind === ScriptKind.External || isPlainJs || isCheckJs || sourceFile.scriptKind === ScriptKind.Deferred);
let bindDiagnostics: readonly Diagnostic[] = includeBindAndCheckDiagnostics ? sourceFile.bindDiagnostics : emptyArray;
let checkDiagnostics = includeBindAndCheckDiagnostics ? typeChecker.getDiagnostics(sourceFile, cancellationToken) : emptyArray;
let checkDiagnostics = includeBindAndCheckDiagnostics ? typeChecker.getDiagnostics(sourceFile, cancellationToken, nodesToCheck) : emptyArray;
if (isPlainJs) {
bindDiagnostics = filter(bindDiagnostics, d => plainJSErrors.has(d.code));
checkDiagnostics = filter(checkDiagnostics, d => plainJSErrors.has(d.code));
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4630,7 +4630,7 @@ export interface Program extends ScriptReferenceHost {
getGlobalDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[];
getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[];
/** The first time this is called, it will return global diagnostics (no location). */
getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[];
getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken, nodesToCheck?: Node[]): readonly Diagnostic[];
getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[];
getConfigFileParsingDiagnostics(): readonly Diagnostic[];
/** @internal */ getSuggestionDiagnostics(sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[];
Expand Down Expand Up @@ -5112,7 +5112,7 @@ export interface TypeChecker {
/** @internal */ getSymbolWalker(accept?: (symbol: Symbol) => boolean): SymbolWalker;

// Should not be called directly. Should only be accessed through the Program instance.
/** @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[];
/** @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken, nodesToCheck?: Node[]): Diagnostic[];
/** @internal */ getGlobalDiagnostics(): Diagnostic[];
/** @internal */ getEmitResolver(sourceFile?: SourceFile, cancellationToken?: CancellationToken): EmitResolver;

Expand Down
101 changes: 101 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10639,6 +10639,107 @@ export function getNameFromImportAttribute(node: ImportAttribute) {
return isIdentifier(node.name) ? node.name.escapedText : escapeLeadingUnderscores(node.name.text);
}

/** @internal */
export function isSourceElement(node: Node): boolean {
switch (node.kind) {
case SyntaxKind.TypeParameter:
case SyntaxKind.Parameter:
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
case SyntaxKind.ConstructorType:
case SyntaxKind.FunctionType:
case SyntaxKind.CallSignature:
case SyntaxKind.ConstructSignature:
case SyntaxKind.IndexSignature:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.ClassStaticBlockDeclaration:
case SyntaxKind.Constructor:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.TypeReference:
case SyntaxKind.TypePredicate:
case SyntaxKind.TypeQuery:
case SyntaxKind.TypeLiteral:
case SyntaxKind.ArrayType:
case SyntaxKind.TupleType:
case SyntaxKind.UnionType:
case SyntaxKind.IntersectionType:
case SyntaxKind.ParenthesizedType:
case SyntaxKind.OptionalType:
case SyntaxKind.RestType:
case SyntaxKind.ThisType:
case SyntaxKind.TypeOperator:
case SyntaxKind.ConditionalType:
case SyntaxKind.InferType:
case SyntaxKind.TemplateLiteralType:
case SyntaxKind.ImportType:
case SyntaxKind.NamedTupleMember:
case SyntaxKind.JSDocAugmentsTag:
case SyntaxKind.JSDocImplementsTag:
case SyntaxKind.JSDocTypedefTag:
case SyntaxKind.JSDocCallbackTag:
case SyntaxKind.JSDocEnumTag:
case SyntaxKind.JSDocTemplateTag:
case SyntaxKind.JSDocTypeTag:
case SyntaxKind.JSDocLink:
case SyntaxKind.JSDocLinkCode:
case SyntaxKind.JSDocLinkPlain:
case SyntaxKind.JSDocParameterTag:
case SyntaxKind.JSDocPropertyTag:
case SyntaxKind.JSDocFunctionType:
case SyntaxKind.JSDocNonNullableType:
case SyntaxKind.JSDocNullableType:
case SyntaxKind.JSDocAllType:
case SyntaxKind.JSDocUnknownType:
case SyntaxKind.JSDocTypeLiteral:
case SyntaxKind.JSDocVariadicType:
case SyntaxKind.JSDocTypeExpression:
case SyntaxKind.JSDocPublicTag:
case SyntaxKind.JSDocProtectedTag:
case SyntaxKind.JSDocPrivateTag:
case SyntaxKind.JSDocSatisfiesTag:
case SyntaxKind.JSDocThisTag:
case SyntaxKind.IndexedAccessType:
case SyntaxKind.MappedType:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.Block:
case SyntaxKind.ModuleBlock:
case SyntaxKind.VariableStatement:
case SyntaxKind.ExpressionStatement:
case SyntaxKind.IfStatement:
case SyntaxKind.DoStatement:
case SyntaxKind.WhileStatement:
case SyntaxKind.ForStatement:
case SyntaxKind.ForInStatement:
case SyntaxKind.ForOfStatement:
case SyntaxKind.ContinueStatement:
case SyntaxKind.BreakStatement:
case SyntaxKind.ReturnStatement:
case SyntaxKind.WithStatement:
case SyntaxKind.SwitchStatement:
case SyntaxKind.LabeledStatement:
case SyntaxKind.ThrowStatement:
case SyntaxKind.TryStatement:
case SyntaxKind.VariableDeclaration:
case SyntaxKind.BindingElement:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.ModuleDeclaration:
case SyntaxKind.ImportDeclaration:
case SyntaxKind.ImportEqualsDeclaration:
case SyntaxKind.ExportDeclaration:
case SyntaxKind.ExportAssignment:
case SyntaxKind.EmptyStatement:
case SyntaxKind.DebuggerStatement:
case SyntaxKind.MissingDeclaration:
return true;
}
return false;
}

/** @internal */
export function isSyntacticallyString(expr: Expression): boolean {
expr = skipOuterExpressions(expr);
Expand Down
48 changes: 42 additions & 6 deletions src/compiler/utilitiesPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,15 +345,23 @@ export function textSpanContainsPosition(span: TextSpan, position: number) {
}

/** @internal */
export function textRangeContainsPositionInclusive(span: TextRange, position: number): boolean {
return position >= span.pos && position <= span.end;
export function textRangeContainsPositionInclusive(range: TextRange, position: number): boolean {
return position >= range.pos && position <= range.end;
}

// Returns true if 'span' contains 'other'.
export function textSpanContainsTextSpan(span: TextSpan, other: TextSpan) {
return other.start >= span.start && textSpanEnd(other) <= textSpanEnd(span);
}

export function textSpanContainsTextRange(span: TextSpan, range: TextRange) {
return range.pos >= span.start && range.end <= textSpanEnd(span);
}

export function textRangeContainsTextSpan(range: TextRange, span: TextSpan) {
return span.start >= range.pos && textSpanEnd(span) <= range.end;
}

export function textSpanOverlapsWith(span: TextSpan, other: TextSpan) {
return textSpanOverlap(span, other) !== undefined;
}
Expand All @@ -363,30 +371,58 @@ export function textSpanOverlap(span1: TextSpan, span2: TextSpan): TextSpan | un
return overlap && overlap.length === 0 ? undefined : overlap;
}

export function textSpanIntersectsWithTextSpan(span: TextSpan, other: TextSpan) {
export function textSpanIntersectsWithTextSpan(span: TextSpan, other: TextSpan): boolean {
return decodedTextSpanIntersectsWith(span.start, span.length, other.start, other.length);
}

export function textSpanIntersectsWith(span: TextSpan, start: number, length: number) {
export function textSpanIntersectsWith(span: TextSpan, start: number, length: number): boolean {
return decodedTextSpanIntersectsWith(span.start, span.length, start, length);
}

export function decodedTextSpanIntersectsWith(start1: number, length1: number, start2: number, length2: number) {
export function decodedTextSpanIntersectsWith(start1: number, length1: number, start2: number, length2: number): boolean {
const end1 = start1 + length1;
const end2 = start2 + length2;
return start2 <= end1 && end2 >= start1;
}

export function textSpanIntersectsWithPosition(span: TextSpan, position: number) {
export function textSpanIntersectsWithPosition(span: TextSpan, position: number): boolean {
return position <= textSpanEnd(span) && position >= span.start;
}

export function textRangeIntersectsWithTextSpan(range: TextRange, span: TextSpan): boolean {
return textSpanIntersectsWith(span, range.pos, range.end - range.pos);
}

export function textSpanIntersection(span1: TextSpan, span2: TextSpan): TextSpan | undefined {
const start = Math.max(span1.start, span2.start);
const end = Math.min(textSpanEnd(span1), textSpanEnd(span2));
return start <= end ? createTextSpanFromBounds(start, end) : undefined;
}

/** @internal */
export function normalizeSpans(spans: readonly TextSpan[]): TextSpan[] {
gabritto marked this conversation as resolved.
Show resolved Hide resolved
spans = spans.filter(span => span.length > 0).sort((a, b) => {
return a.start !== b.start ? a.start - b.start : a.length - b.length;
});

const result: TextSpan[] = [];
let i = 0;
while (i < spans.length) {
let span = spans[i];
let j = i + 1;
while (j < spans.length && textSpanIntersectsWithTextSpan(span, spans[j])) {
const start = Math.min(span.start, spans[j].start);
const end = Math.max(textSpanEnd(span), textSpanEnd(spans[j]));
span = createTextSpanFromBounds(start, end);
j++;
}
i = j;
result.push(span);
}

return result;
}

export function createTextSpan(start: number, length: number): TextSpan {
if (start < 0) {
throw new Error("start < 0");
Expand Down
Loading