diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 0ca37715b9f17..177d98c37ba41 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -3045,6 +3045,10 @@ Actual: ${stringify(fullActual)}`); } } + public verifyRefactorsAvailable(names: ReadonlyArray): void { + assert.deepEqual(unique(this.getApplicableRefactors(this.getSelection()), r => r.name), names); + } + public verifyRefactor({ name, actionName, refactors }: FourSlashInterface.VerifyRefactorOptions) { const actualRefactors = this.getApplicableRefactors(this.getSelection()).filter(r => r.name === name && r.actions.some(a => a.name === actionName)); this.assertObjectsEqual(actualRefactors, refactors); @@ -3815,7 +3819,7 @@ ${code} } /** Collects an array of unique outputs. */ - function unique(inputs: T[], getOutput: (t: T) => string): string[] { + function unique(inputs: ReadonlyArray, getOutput: (t: T) => string): string[] { const set = ts.createMap(); for (const input of inputs) { const out = getOutput(input); @@ -4106,6 +4110,10 @@ namespace FourSlashInterface { this.state.verifyApplicableRefactorAvailableForRange(this.negative); } + public refactorsAvailable(names: ReadonlyArray): void { + this.state.verifyRefactorsAvailable(names); + } + public refactor(options: VerifyRefactorOptions) { this.state.verifyRefactor(options); } diff --git a/src/services/refactors/generateGetAccessorAndSetAccessor.ts b/src/services/refactors/generateGetAccessorAndSetAccessor.ts index 2e4f11c4b279f..b7240f2e985a3 100644 --- a/src/services/refactors/generateGetAccessorAndSetAccessor.ts +++ b/src/services/refactors/generateGetAccessorAndSetAccessor.ts @@ -9,20 +9,19 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor { type ContainerDeclaration = ClassLikeDeclaration | ObjectLiteralExpression; interface Info { - container: ContainerDeclaration; - isStatic: boolean; - isReadonly: boolean; - type: TypeNode | undefined; - declaration: AcceptedDeclaration; - fieldName: AcceptedNameType; - accessorName: AcceptedNameType; - originalName: AcceptedNameType; - renameAccessor: boolean; + readonly container: ContainerDeclaration; + readonly isStatic: boolean; + readonly isReadonly: boolean; + readonly type: TypeNode | undefined; + readonly declaration: AcceptedDeclaration; + readonly fieldName: AcceptedNameType; + readonly accessorName: AcceptedNameType; + readonly originalName: AcceptedNameType; + readonly renameAccessor: boolean; } function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined { - const { file } = context; - if (!getConvertibleFieldAtPosition(context, file)) return undefined; + if (!getConvertibleFieldAtPosition(context)) return undefined; return [{ name: actionName, @@ -39,7 +38,7 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor { function getEditsForAction(context: RefactorContext, _actionName: string): RefactorEditInfo | undefined { const { file } = context; - const fieldInfo = getConvertibleFieldAtPosition(context, file); + const fieldInfo = getConvertibleFieldAtPosition(context); if (!fieldInfo) return undefined; const isJS = isSourceFileJavaScript(file); @@ -117,14 +116,14 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor { return name.charCodeAt(0) === CharacterCodes._; } - function getConvertibleFieldAtPosition(context: RefactorContext, file: SourceFile): Info | undefined { - const { startPosition, endPosition } = context; + function getConvertibleFieldAtPosition(context: RefactorContext): Info | undefined { + const { file, startPosition, endPosition } = context; const node = getTokenAtPosition(file, startPosition, /*includeJsDocComment*/ false); const declaration = findAncestor(node.parent, isAcceptedDeclaration); // make sure declaration have AccessibilityModifier or Static Modifier or Readonly Modifier const meaning = ModifierFlags.AccessibilityModifier | ModifierFlags.Static | ModifierFlags.Readonly; - if (!declaration || !rangeOverlapsWithStartEnd(declaration.name, startPosition, endPosition!) // TODO: GH#18217 + if (!declaration || !nodeOverlapsWithStartEnd(declaration.name, file, startPosition, endPosition!) // TODO: GH#18217 || !isConvertibleName(declaration.name) || (getModifierFlags(declaration) | meaning) !== meaning) return undefined; const name = declaration.name.text; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 9715d124d7eee..e3f4f6523fb16 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -439,6 +439,10 @@ namespace ts { return startEndOverlapsWithStartEnd(r1.pos, r1.end, start, end); } + export function nodeOverlapsWithStartEnd(node: Node, sourceFile: SourceFile, start: number, end: number) { + return startEndOverlapsWithStartEnd(node.getStart(sourceFile), node.end, start, end); + } + export function startEndOverlapsWithStartEnd(start1: number, end1: number, start2: number, end2: number) { const start = Math.max(start1, start2); const end = Math.min(end1, end2); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index a9fe9a32556dc..1dd5bded0b767 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -10670,6 +10670,7 @@ declare namespace ts { function startEndContainsRange(start: number, end: number, range: TextRange): boolean; function rangeContainsStartEnd(range: TextRange, start: number, end: number): boolean; function rangeOverlapsWithStartEnd(r1: TextRange, start: number, end: number): boolean; + function nodeOverlapsWithStartEnd(node: Node, sourceFile: SourceFile, start: number, end: number): boolean; function startEndOverlapsWithStartEnd(start1: number, end1: number, start2: number, end2: number): boolean; /** * Assumes `candidate.start <= position` holds. diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index a70d6dcf424e1..89e1948ce77cb 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -190,6 +190,7 @@ declare namespace FourSlashInterface { applicableRefactorAvailableForRange(): void; refactorAvailable(name: string, actionName?: string): void; + refactorsAvailable(names: ReadonlyArray): void; refactor(options: { name: string; actionName: string; diff --git a/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess_notOnWhitespace.ts b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess_notOnWhitespace.ts new file mode 100644 index 0000000000000..9ba208b8e9547 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess_notOnWhitespace.ts @@ -0,0 +1,8 @@ +/// + +////class A { +/////*a*/ /*b*/p = 0; +////} + +goTo.select("a", "b"); +verify.refactorsAvailable([]);