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

(fix) if conditions control flow #846

Merged
merged 19 commits into from
Mar 10, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ export interface DocumentSnapshot extends ts.IScriptSnapshot {
* in order to prevent memory leaks.
*/
destroyFragment(): void;
/**
* Convenience function for getText(0, getLength())
*/
getFullText(): string;
}

/**
Expand Down Expand Up @@ -221,6 +225,10 @@ export class SvelteDocumentSnapshot implements DocumentSnapshot {
return this.text.length;
}

getFullText() {
return this.text;
}

getChangeRange() {
return undefined;
}
Expand Down Expand Up @@ -301,6 +309,10 @@ export class JSOrTSDocumentSnapshot
return this.text.length;
}

getFullText() {
return this.text;
}

getChangeRange() {
return undefined;
}
Expand Down
38 changes: 17 additions & 21 deletions packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
getTextInRange
} from '../../lib/documents';
import { LSConfigManager, LSTypescriptConfig } from '../../ls-config';
import { pathToUrl } from '../../utils';
import { isNotNullOrUndefined, pathToUrl } from '../../utils';
import {
AppCompletionItem,
AppCompletionList,
Expand All @@ -49,7 +49,6 @@ import {
SemanticTokensProvider,
UpdateTsOrJsFile
} from '../interfaces';
import { SnapshotFragment } from './DocumentSnapshot';
import { CodeActionsProviderImpl } from './features/CodeActionsProvider';
import {
CompletionEntryWithIdentifer,
Expand All @@ -67,6 +66,7 @@ import { SelectionRangeProviderImpl } from './features/SelectionRangeProvider';
import { SignatureHelpProviderImpl } from './features/SignatureHelpProvider';
import { SnapshotManager } from './SnapshotManager';
import { SemanticTokensProviderImpl } from './features/SemanticTokensProvider';
import { isNoTextSpanInGeneratedCode, SnapshotFragmentMap } from './features/utils';

export class TypeScriptPlugin
implements
Expand Down Expand Up @@ -263,35 +263,35 @@ export class TypeScriptPlugin
}

const { lang, tsDoc } = this.getLSAndTSDoc(document);
const fragment = await tsDoc.getFragment();
const mainFragment = await tsDoc.getFragment();

const defs = lang.getDefinitionAndBoundSpan(
tsDoc.filePath,
fragment.offsetAt(fragment.getGeneratedPosition(position))
mainFragment.offsetAt(mainFragment.getGeneratedPosition(position))
);

if (!defs || !defs.definitions) {
return [];
}

const docs = new Map<string, SnapshotFragment>([[tsDoc.filePath, fragment]]);
const docs = new SnapshotFragmentMap(this.lsAndTsDocResolver);
docs.set(tsDoc.filePath, { fragment: mainFragment, snapshot: tsDoc });

return await Promise.all(
const result = await Promise.all(
defs.definitions.map(async (def) => {
let defDoc = docs.get(def.fileName);
if (!defDoc) {
defDoc = await this.getSnapshot(def.fileName).getFragment();
docs.set(def.fileName, defDoc);
const { fragment, snapshot } = await docs.retrieve(def.fileName);

if (isNoTextSpanInGeneratedCode(snapshot.getFullText(), def.textSpan)) {
return LocationLink.create(
pathToUrl(def.fileName),
convertToLocationRange(fragment, def.textSpan),
convertToLocationRange(fragment, def.textSpan),
convertToLocationRange(mainFragment, defs.textSpan)
);
}

return LocationLink.create(
pathToUrl(def.fileName),
convertToLocationRange(defDoc, def.textSpan),
convertToLocationRange(defDoc, def.textSpan),
convertToLocationRange(fragment, defs.textSpan)
);
})
);
return result.filter(isNotNullOrUndefined);
}

async prepareRename(document: Document, position: Position): Promise<Range | null> {
Expand Down Expand Up @@ -436,10 +436,6 @@ export class TypeScriptPlugin
return this.lsAndTsDocResolver.getLSAndTSDoc(document);
}

private getSnapshot(filePath: string, document?: Document) {
return this.lsAndTsDocResolver.getSnapshot(filePath, document);
}

/**
*
* @internal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import {
WorkspaceEdit
} from 'vscode-languageserver';
import { Document, mapRangeToOriginal, isRangeInTag, isInTag } from '../../../lib/documents';
import { pathToUrl, flatten } from '../../../utils';
import { pathToUrl, flatten, isNotNullOrUndefined } from '../../../utils';
import { CodeActionsProvider } from '../../interfaces';
import { SnapshotFragment, SvelteSnapshotFragment } from '../DocumentSnapshot';
import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
import { convertRange } from '../utils';

import ts from 'typescript';
import { CompletionsProviderImpl } from './CompletionProvider';
import { isNoTextSpanInGeneratedCode, SnapshotFragmentMap } from './utils';

interface RefactorArgs {
type: 'refactor';
Expand Down Expand Up @@ -134,53 +135,65 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
userPreferences
);

const docs = new Map<string, SnapshotFragment>([[tsDoc.filePath, fragment]]);
const docs = new SnapshotFragmentMap(this.lsAndTsDocResolver);
docs.set(tsDoc.filePath, { fragment, snapshot: tsDoc });

return await Promise.all(
codeFixes.map(async (fix) => {
const documentChanges = await Promise.all(
fix.changes.map(async (change) => {
const doc =
docs.get(change.fileName) ??
(await this.getAndCacheCodeActionDoc(change, docs));
const { snapshot, fragment } = await docs.retrieve(change.fileName);
return TextDocumentEdit.create(
VersionedTextDocumentIdentifier.create(pathToUrl(change.fileName), 0),
change.textChanges.map((edit) => {
if (
fix.fixName === 'import' &&
doc instanceof SvelteSnapshotFragment
) {
return this.completionProvider.codeActionChangeToTextEdit(
document,
doc,
edit,
true,
isInTag(range.start, document.scriptInfo) ||
isInTag(range.start, document.moduleScriptInfo)
);
}

let originalRange = mapRangeToOriginal(
doc,
convertRange(doc, edit.span)
);
if (fix.fixName === 'unusedIdentifier') {
originalRange = this.checkRemoveImportCodeActionRange(
edit,
doc,
originalRange
change.textChanges
.map((edit) => {
if (
fix.fixName === 'import' &&
fragment instanceof SvelteSnapshotFragment
) {
return this.completionProvider.codeActionChangeToTextEdit(
document,
fragment,
edit,
true,
isInTag(range.start, document.scriptInfo) ||
isInTag(range.start, document.moduleScriptInfo)
);
}

if (
!isNoTextSpanInGeneratedCode(
snapshot.getFullText(),
edit.span
)
) {
return undefined;
}

let originalRange = mapRangeToOriginal(
fragment,
convertRange(fragment, edit.span)
);
}

if (fix.fixName === 'fixMissingFunctionDeclaration') {
originalRange = this.checkEndOfFileCodeInsert(
originalRange,
range,
document
);
}

return TextEdit.replace(originalRange, edit.newText);
})
if (fix.fixName === 'unusedIdentifier') {
originalRange = this.checkRemoveImportCodeActionRange(
edit,
fragment,
originalRange
);
}

if (fix.fixName === 'fixMissingFunctionDeclaration') {
originalRange = this.checkEndOfFileCodeInsert(
originalRange,
range,
document
);
}

return TextEdit.replace(originalRange, edit.newText);
})
.filter(isNotNullOrUndefined)
);
})
);
Expand All @@ -195,15 +208,6 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
);
}

private async getAndCacheCodeActionDoc(
change: ts.FileTextChanges,
cache: Map<string, SnapshotFragment>
) {
const doc = await this.getSnapshot(change.fileName).getFragment();
cache.set(change.fileName, doc);
return doc;
}

private async getApplicableRefactors(document: Document, range: Range): Promise<CodeAction[]> {
if (
!isRangeInTag(range, document.scriptInfo) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { DiagnosticsProvider } from '../../interfaces';
import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
import { convertRange, mapSeverity } from '../utils';
import { SvelteDocumentSnapshot } from '../DocumentSnapshot';
import { isInGeneratedCode } from './utils';

export class DiagnosticsProviderImpl implements DiagnosticsProvider {
constructor(private readonly lsAndTsDocResolver: LSAndTSDocResolver) {}
Expand Down Expand Up @@ -40,6 +41,7 @@ export class DiagnosticsProviderImpl implements DiagnosticsProvider {
const fragment = await tsDoc.getFragment();

return diagnostics
.filter(isNotGenerated(tsDoc.getText(0, tsDoc.getLength())))
.map<Diagnostic>((diagnostic) => ({
range: convertRange(tsDoc, diagnostic),
severity: mapSeverity(diagnostic.category),
Expand Down Expand Up @@ -208,3 +210,16 @@ function swapRangeStartEndIfNecessary(diag: Diagnostic): Diagnostic {
}
return diag;
}

/**
* Checks if diagnostic is not within a section that should be completely ignored
* because it's purely generated.
*/
function isNotGenerated(text: string) {
return (diagnostic: ts.Diagnostic) => {
if (diagnostic.start === undefined || diagnostic.length === undefined) {
return true;
}
return !isInGeneratedCode(text, diagnostic.start, diagnostic.start + diagnostic.length);
};
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import ts from 'typescript';
import { Location, Position, ReferenceContext } from 'vscode-languageserver';
import { Document } from '../../../lib/documents';
import { pathToUrl } from '../../../utils';
import { FindReferencesProvider } from '../../interfaces';
import { SnapshotFragment } from '../DocumentSnapshot';
import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
import { convertToLocationRange } from '../utils';
import { isNoTextSpanInGeneratedCode, SnapshotFragmentMap } from './utils';

export class FindReferencesProviderImpl implements FindReferencesProvider {
constructor(private readonly lsAndTsDocResolver: LSAndTSDocResolver) {}
Expand All @@ -25,17 +26,15 @@ export class FindReferencesProviderImpl implements FindReferencesProvider {
return null;
}

const docs = new Map<string, SnapshotFragment>([[tsDoc.filePath, fragment]]);
const docs = new SnapshotFragmentMap(this.lsAndTsDocResolver);
docs.set(tsDoc.filePath, { fragment, snapshot: tsDoc });

return await Promise.all(
references
.filter((ref) => context.includeDeclaration || !ref.isDefinition)
.filter(notInGeneratedCode(tsDoc.getFullText()))
.map(async (ref) => {
let defDoc = docs.get(ref.fileName);
if (!defDoc) {
defDoc = await this.getSnapshot(ref.fileName).getFragment();
docs.set(ref.fileName, defDoc);
}
const defDoc = await docs.retrieveFragment(ref.fileName);

return Location.create(
pathToUrl(ref.fileName),
Expand All @@ -48,8 +47,10 @@ export class FindReferencesProviderImpl implements FindReferencesProvider {
private getLSAndTSDoc(document: Document) {
return this.lsAndTsDocResolver.getLSAndTSDoc(document);
}
}

private getSnapshot(filePath: string, document?: Document) {
return this.lsAndTsDocResolver.getSnapshot(filePath, document);
}
function notInGeneratedCode(text: string) {
return (ref: ts.ReferenceEntry) => {
return isNoTextSpanInGeneratedCode(text, ref.textSpan);
};
}
Loading