Skip to content

Commit 0e061d3

Browse files
committed
feat: new powerful declare missing property snippet codefix that by default works only in the same file, but can be extended with a new setting. Was designed specificially for styles.create pattern!
1 parent 26d33a0 commit 0e061d3

File tree

5 files changed

+69
-4
lines changed

5 files changed

+69
-4
lines changed

.vscode/settings.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
"typescript/lib/tsserverlibrary": {
44
"namespace": "ts",
55
"addImport": false
6+
},
7+
"typescript": {
8+
"namespace": "ts",
9+
"addImport": false
610
}
711
},
812
"typescript.preferences.autoImportFileExcludePatterns": [
@@ -24,5 +28,5 @@
2428
"vitest.showFailMessages": true,
2529
"vitest.include": [
2630
"typescript/test/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"
27-
]
31+
],
2832
}

src/configurationType.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,10 @@ export type Configuration = {
680680
// bigFilesThreshold: number
681681
/** @default false */
682682
enableHooksFile: boolean
683+
/**
684+
* @default false
685+
*/
686+
declareMissingPropertyQuickfixOtherFiles: boolean
683687
}
684688

685689
// scrapped using search editor. config: caseInsensitive, context lines: 0, regex: const fix\w+ = "[^ ]+"

typescript/src/codeActions/extended/declareMissingProperties.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,52 @@ export default {
55
codes: [2339],
66
kind: 'quickfix',
77
title: 'Declare missing property',
8-
tryToApply({ sourceFile, node }) {
8+
tryToApply({ sourceFile, node, c, languageService }) {
99
const param = matchParents(node, ['Identifier', 'BindingElement', 'ObjectBindingPattern', 'Parameter'])
10+
const objAccess = matchParents(node, ['Identifier', 'PropertyAccessExpression'])
11+
const missingPropName = (node as ts.Identifier).text
12+
if (objAccess) {
13+
const checker = languageService.getProgram()!.getTypeChecker()!
14+
const type = checker.getContextualType(objAccess.expression) || checker.getTypeAtLocation(objAccess.expression)
15+
const props = type
16+
.getProperties()
17+
.map(type => {
18+
const node = type.declarations?.find(declaration => {
19+
return c('declareMissingPropertyQuickfixOtherFiles') || declaration.getSourceFile().fileName === sourceFile.fileName
20+
})
21+
if (node === undefined) return undefined!
22+
return { name: type.name, node }
23+
})
24+
.filter(Boolean)
25+
// TARGET PROP
26+
const propInsertAfter = props.find(prop => missingPropName.startsWith(prop.name)) ?? props.at(-1)
27+
if (propInsertAfter) {
28+
const propInsertParent = propInsertAfter.node.parent
29+
const sameParentLiteralProps = props.filter(
30+
prop => prop.node.parent === propInsertParent && ts.isPropertyAssignment(prop.node) && !ts.isIdentifier(prop.node.initializer),
31+
)
32+
const insertObject =
33+
sameParentLiteralProps.length > 0 &&
34+
sameParentLiteralProps.every(sameParentProp => ts.isObjectLiteralExpression((sameParentProp.node as ts.PropertyAssignment).initializer))
35+
const insertPos = propInsertAfter.node.end
36+
const insertComma = sourceFile.getFullText().slice(insertPos - 1, insertPos) !== ','
37+
const getLine = pos => sourceFile.getLineAndCharacterOfPosition(pos).line
38+
const insertNewLine = getLine(propInsertAfter.node.pos) !== getLine(propInsertAfter.node.end)
39+
const insertText = `${insertComma ? ',' : ''}${insertNewLine ? '\n' : ' '}${missingPropName}`
40+
const snippet = insertObject ? `: {${insertNewLine ? '\n\t' : ''}$0${insertNewLine ? '\n' : ''}}` : `$0`
41+
return {
42+
snippetEdits: [
43+
{
44+
newText: `${tsFull.escapeSnippetText(insertText)}${snippet}`,
45+
span: {
46+
length: 0,
47+
start: insertPos,
48+
},
49+
},
50+
],
51+
}
52+
}
53+
}
1054
if (param) {
1155
// special react pattern
1256
if (ts.isArrowFunction(param.parent) && ts.isVariableDeclaration(param.parent.parent)) {
@@ -20,7 +64,7 @@ export default {
2064
const hasMembers = param.type.members.length > 0
2165
const insertPos = param.type.members.at(-1)?.end ?? param.type.end - 1
2266
const insertComma = hasMembers && sourceFile.getFullText().slice(insertPos - 1, insertPos) !== ','
23-
let insertText = (node as ts.Identifier).text
67+
let insertText = missingPropName
2468
if (insertComma) insertText = `, ${insertText}`
2569
// alternatively only one snippetEdit could be used with tsFull.escapeSnippetText(insertText) + $0
2670
return {

typescript/src/codeActions/getCodeActions.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { compact } from '@zardoy/utils'
22
import { Except } from 'type-fest'
33
import { findChildContainingExactPosition, findChildContainingPosition } from '../utils'
44
import { ApplyExtendedCodeActionResult, IpcExtendedCodeAction } from '../ipcTypes'
5+
import { GetConfig } from '../types'
56
import objectSwapKeysAndValues from './custom/objectSwapKeysAndValues'
67
import changeStringReplaceToRegex from './custom/changeStringReplaceToRegex'
78
import splitDeclarationAndInitialization from './custom/splitDeclarationAndInitialization'
@@ -56,6 +57,7 @@ export type ApplyExtendedCodeAction = (options: {
5657
/** undefined when no edits is requested */
5758
formatOptions: ts.FormatCodeSettings | undefined
5859
languageService: ts.LanguageService
60+
c: GetConfig
5961
// languageServiceHost: ts.LanguageServiceHost
6062
}) => ApplyExtendedCodeActionResult | boolean | undefined
6163

@@ -80,6 +82,7 @@ export const getExtendedCodeActions = <T extends string | undefined>(
8082
// languageServiceHost: ts.LanguageServiceHost,
8183
formatOptions: ts.FormatCodeSettings | undefined,
8284
applyCodeActionTitle: T,
85+
config: GetConfig,
8386
filterErrorCodes?: number[],
8487
): T extends undefined ? ExtendedCodeAction[] : ApplyExtendedCodeActionResult => {
8588
const range = typeof positionOrRange !== 'number' && positionOrRange.pos !== positionOrRange.end ? positionOrRange : undefined
@@ -93,6 +96,7 @@ export const getExtendedCodeActions = <T extends string | undefined>(
9396
position,
9497
range,
9598
sourceFile,
99+
c: config,
96100
}
97101
if (applyCodeActionTitle) {
98102
const codeAction = extendedCodeActions.find(codeAction => codeAction.title === applyCodeActionTitle)

typescript/src/specialCommands/handle.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,15 @@ export default (
4040
const node = findChildContainingPosition(ts, sourceFile, position)
4141
const posEnd = { pos: specialCommandArg.range[0], end: specialCommandArg.range[1] }
4242

43-
const extendedCodeActions = getExtendedCodeActions(sourceFile, posEnd, languageService, undefined, undefined, specialCommandArg.diagnostics)
43+
const extendedCodeActions = getExtendedCodeActions(
44+
sourceFile,
45+
posEnd,
46+
languageService,
47+
undefined,
48+
undefined,
49+
configuration,
50+
specialCommandArg.diagnostics,
51+
)
4452
return {
4553
turnArrayIntoObject: objectIntoArrayConverters(posEnd, node, undefined),
4654
extendedCodeActions,
@@ -56,6 +64,7 @@ export default (
5664
languageService,
5765
formatOptions,
5866
applyCodeActionTitle,
67+
configuration,
5968
) satisfies RequestOutputTypes['getExtendedCodeActionEdits']
6069
}
6170
if (specialCommand === 'twoStepCodeActionSecondStep') {

0 commit comments

Comments
 (0)