Skip to content

Commit

Permalink
feat: autofill objects returned by functions/methods
Browse files Browse the repository at this point in the history
fix #187
  • Loading branch information
tamj0rd2 committed May 11, 2021
1 parent a758170 commit 0250906
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 33 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ situations where a nested object has missing members:

![Missing constructor members](gifs/missing-constructor-argument-members.gif)

#### Missing function/method return members

![Missing function/method return members](gifs/missing-method-return-members.gif)

## Requirements

N/A
Expand Down
Binary file added gifs/missing-method-return-members.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 7 additions & 5 deletions packages/e2e/src/tests/extension.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,16 @@ describe('Acceptance tests', () => {
const testingDocument = await vscode.workspace.openTextDocument(testFileUri)
await vscode.window.showTextDocument(testingDocument)

const methodReturnAction = await getCodeAction(testingDocument, `return {}`, ACTION_NAME)
await vscode.workspace.applyEdit(methodReturnAction.edit!)
expect(getAllDocumentText(testingDocument)).toContain(`{\n status: 'todo'\n}`)

const constructorAction = await getCodeAction(testingDocument, `{ timeout: 456 }`, ACTION_NAME)
await vscode.workspace.applyEdit(constructorAction.edit!)

const methodAction = await getCodeAction(testingDocument, `makeRequest({})`, ACTION_NAME)
await vscode.workspace.applyEdit(methodAction.edit!)

const documentText = getAllDocumentText(testingDocument)
expect(documentText).toContain(await readFixture('new-http-client'))
const methodCallAction = await getCodeAction(testingDocument, `makeRequest({})`, ACTION_NAME)
await vscode.workspace.applyEdit(methodCallAction.edit!)
expect(getAllDocumentText(testingDocument)).toContain(await readFixture('new-http-client'))
})
})
})
Expand Down
4 changes: 4 additions & 0 deletions packages/extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ situations where a nested object has missing members:

![Missing constructor members](gifs/missing-constructor-argument-members.gif)

#### Missing function/method return members

![Missing function/method return members](gifs/missing-method-return-members.gif)

## Requirements

N/A
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions packages/plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ situations where a nested object has missing members:

![Missing constructor members](gifs/missing-constructor-argument-members.gif)

#### Missing function/method return members

![Missing function/method return members](gifs/missing-method-return-members.gif)

## Requirements

N/A
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -292,53 +292,97 @@ describe('declareMissingObjectMembers', () => {
expect(newText).toMatchInitializer({ greeting: 'todo', name: 'todo' })
})

it('works for class constructors', () => {
const initializer = '{}'
it('works for function returns', () => {
const [filePath, fileContent] = FsMocker.instance.addFile(/* ts */ `
interface TargetType {
greeting: string
name: string
}
class MyClass {
constructor(target: TargetType) {}
function myFunc(): TargetType {
return {}
}
new MyClass(${initializer})
`)

const newText = getNewText({
filePath,
initializerPos: getNodeRange(fileContent, initializer, { index: 1 }),
initializerPos: getNodeRange(fileContent, '{}', { index: 0 }),
errorPos: getNodeRange(fileContent, 'return {}'),
})

expect(newText).toMatchInitializer({ greeting: 'todo', name: 'todo' })
})

it('works for method calls', () => {
const initializer = '{}'
const [filePath, fileContent] = FsMocker.instance.addFile(/* ts */ `
interface TargetType {
greeting: string
name: string
}
describe('classes', () => {
it('works for class constructors', () => {
const initializer = '{}'
const [filePath, fileContent] = FsMocker.instance.addFile(/* ts */ `
interface TargetType {
greeting: string
name: string
}
class MyClass {
constructor(target: TargetType) {}
}
new MyClass(${initializer})
`)

class MyClass {
public method(targetType: TargetType) {}
}
const newText = getNewText({
filePath,
initializerPos: getNodeRange(fileContent, initializer, { index: 1 }),
})

new MyClass().method(${initializer})
`)
expect(newText).toMatchInitializer({ greeting: 'todo', name: 'todo' })
})

const newText = getNewText({
filePath,
initializerPos: getNodeRange(fileContent, initializer, { index: 1 }),
it('works for method calls', () => {
const initializer = '{}'
const [filePath, fileContent] = FsMocker.instance.addFile(/* ts */ `
interface TargetType {
greeting: string
name: string
}
class MyClass {
public method(targetType: TargetType) {}
}
new MyClass().method(${initializer})
`)

const newText = getNewText({
filePath,
initializerPos: getNodeRange(fileContent, initializer, { index: 1 }),
})

expect(newText).toMatchInitializer({ greeting: 'todo', name: 'todo' })
})

expect(newText).toMatchInitializer({ greeting: 'todo', name: 'todo' })
})
it('works for method returns', () => {
const [filePath, fileContent] = FsMocker.instance.addFile(/* ts */ `
interface TargetType {
greeting: string
name: string
}
class MyClass {
public myMethod(): TargetType {
return {}
}
}
`)

it.todo('works for function and method returns')
const newText = getNewText({
filePath,
initializerPos: getNodeRange(fileContent, '{}', { index: 0 }),
errorPos: getNodeRange(fileContent, 'return {}'),
})

expect(newText).toMatchInitializer({ greeting: 'todo', name: 'todo' })
})
})

describe('scope', () => {
it('can use locals that are in scope', () => {
Expand Down
14 changes: 12 additions & 2 deletions packages/plugin/src/code-fixes/declare-missing-object-members.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ export namespace DeclareMissingObjectMembers {
return errorNode
}

if (ts.isReturnStatement(errorNode)) {
return TSH.cast(errorNode.expression, ts.isObjectLiteralExpression)
}

throw new Error(`Unhandled errorNode type ${ts.SyntaxKind[errorNode.kind]}`)
}

Expand All @@ -73,7 +77,7 @@ export namespace DeclareMissingObjectMembers {
const previousNode = relevantNodes[0]
const node = previousNode.parent

if (ts.isVariableDeclaration(node)) {
if (ts.isVariableDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
const identifier = TSH.cast(node.name, ts.isIdentifier)
return { relevantNodes, topLevelSymbol: TSH.deref(ts, typeChecker, identifier) }
}
Expand All @@ -97,7 +101,7 @@ export namespace DeclareMissingObjectMembers {
relevantNodes.unshift(node)
}

throw new Error('Could find first related type declaration')
throw new Error('Could not find first related type declaration')
}

function deriveExpectedSymbolFromRelatedNodes(
Expand Down Expand Up @@ -128,6 +132,12 @@ export namespace DeclareMissingObjectMembers {
}

if (ts.isFunctionLike(trackedDeclaration)) {
if (ts.isBlock(node)) return trackedSymbol

if (ts.isReturnStatement(node)) {
return TSH.deref(ts, typeChecker, trackedDeclaration.type)
}

return TSH.getTypeForCallArgument(ts, typeChecker, trackedDeclaration, node)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
class HttpClient {
constructor(args: { timeout: number; baseUrl: string; operation: string }) {}

public makeRequest(args: { method: string, endpoint: string }) {}
public makeRequest(args: { method: string, endpoint: string }): { status: string } {
return {}
}
}

const baseUrl = 'https://www.example.com'
Expand Down

0 comments on commit 0250906

Please sign in to comment.