Skip to content

Commit c2dab53

Browse files
committed
feat: add ability to Disable formatting a region of code using ts directives 🚀
ref microsoft/TypeScript#18261
1 parent 11d1e23 commit c2dab53

File tree

6 files changed

+134
-0
lines changed

6 files changed

+134
-0
lines changed

src/extension.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import apiCommands from './apiCommands'
1414
import onCompletionAccepted from './onCompletionAccepted'
1515
import specialCommands from './specialCommands'
1616
import vueVolarSupport from './vueVolarSupport'
17+
import moreCompletions from './moreCompletions'
1718

1819
export const activateTsPlugin = (tsApi: { configurePlugin; onCompletionAccepted }) => {
1920
let webWaitingForConfigSync = false
@@ -65,6 +66,7 @@ export const activateTsPlugin = (tsApi: { configurePlugin; onCompletionAccepted
6566
}
6667

6768
experimentalPostfixes()
69+
moreCompletions()
6870
void registerEmmet()
6971
webImports()
7072
apiCommands()

src/moreCompletions.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import * as vscode from 'vscode'
2+
import { defaultJsSupersetLangsWithVue } from '@zardoy/vscode-utils/build/langs'
3+
4+
export default () => {
5+
vscode.languages.registerCompletionItemProvider(
6+
defaultJsSupersetLangsWithVue,
7+
{
8+
provideCompletionItems(document, position, token, context) {
9+
const regex = /\/\/@?[\w-]*/
10+
let range = document.getWordRangeAtPosition(position, regex)
11+
if (!range) return
12+
const rangeText = document.getText(range)
13+
if (rangeText !== document.lineAt(position).text.trim()) {
14+
return
15+
}
16+
17+
range = range.with(range.start.translate(0, 2), range.end)
18+
const tsDirectives = ['@ts-format-ignore-line', '@ts-format-ignore-region', '@ts-format-ignore-endregion']
19+
return tsDirectives.map((directive, i) => {
20+
const completionItem = new vscode.CompletionItem(directive, vscode.CompletionItemKind.Snippet)
21+
completionItem.range = range
22+
completionItem.sortText = `z${i}`
23+
return completionItem
24+
})
25+
},
26+
},
27+
'@',
28+
)
29+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { GetConfig } from './types'
2+
import { patchMethod } from './utils'
3+
4+
export default (proxy: ts.LanguageService, languageService: ts.LanguageService, c: GetConfig) => {
5+
// const oldGetAllRules = tsFull.formatting.getAllRules;
6+
// tsFull.formatting.getAllRules = () => {
7+
// }
8+
9+
const isFormattingLineIgnored = (sourceFile: ts.SourceFile, position: number) => {
10+
// const sourceFile = languageService.getProgram()!.getSourceFile(fileName)!
11+
const fullText = sourceFile.getFullText()
12+
// check that lines before line are not ignored
13+
const linesBefore = fullText.slice(0, position).split('\n')
14+
if (linesBefore[linesBefore.length - 2]?.trim() === '//@ts-format-ignore-line') {
15+
return true
16+
}
17+
18+
let isInsideIgnoredRegion = false
19+
for (const line of linesBefore) {
20+
if (line.trim() === '//@ts-format-ignore-region') {
21+
isInsideIgnoredRegion = true
22+
}
23+
if (line.trim() === '//@ts-format-ignore-endregion') {
24+
isInsideIgnoredRegion = false
25+
}
26+
}
27+
return isInsideIgnoredRegion
28+
}
29+
// proxy.getFormattingEditsAfterKeystroke = (fileName, position, key, options) => {
30+
// // if (isFormattingLineIgnored(fileName, position)) {
31+
// // return []
32+
// // }
33+
// return languageService.getFormattingEditsAfterKeystroke(fileName, position, key, options)
34+
// }
35+
// proxy.getFormattingEditsForDocument = (fileName, options) => {
36+
// return []
37+
// }
38+
const toPatchFormatMethods = ['formatSelection', 'formatOnOpeningCurly', 'formatOnClosingCurly', 'formatOnSemicolon', 'formatOnEnter']
39+
for (const toPatchFormatMethod of toPatchFormatMethods) {
40+
patchMethod(tsFull.formatting, toPatchFormatMethod as any, oldFn => (...args) => {
41+
const result = oldFn(...args)
42+
const sourceFile = args.find(arg => ts.isSourceFile(arg as any))
43+
return result.filter(({ span }) => {
44+
if (isFormattingLineIgnored(sourceFile as ts.SourceFile, span.start)) {
45+
return false
46+
}
47+
return true
48+
})
49+
})
50+
}
51+
// proxy.getFormattingEditsForRange = (fileName, start, end, options) => {
52+
// return languageService.getFormattingEditsForRange(fileName, start, end, options).filter(({ span }) => {
53+
// if (isFormattingLineIgnored(fileName, span.start)) {
54+
// return false
55+
// }
56+
// return true
57+
// })
58+
// }
59+
}

typescript/src/decorateProxy.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import completionEntryDetails from './completionEntryDetails'
1212
import { GetConfig } from './types'
1313
import lodashGet from 'lodash.get'
1414
import decorateWorkspaceSymbolSearch from './workspaceSymbolSearch'
15+
import decorateFormatFeatures from './decorateFormatFeatures'
1516

1617
/** @internal */
1718
export const thisPluginMarker = '__essentialPluginsMarker__'
@@ -113,6 +114,7 @@ export const decorateLanguageService = (
113114
decorateReferences(proxy, languageService, c)
114115
decorateDocumentHighlights(proxy, languageService, c)
115116
decorateWorkspaceSymbolSearch(proxy, languageService, c, languageServiceHost)
117+
decorateFormatFeatures(proxy, languageService, c)
116118
proxy.findRenameLocations = (fileName, position, findInStrings, findInComments, providePrefixAndSuffixTextForRename) => {
117119
if (overrideRequestPreferences.rename) {
118120
try {

typescript/src/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,15 @@ export function approveCast<T2 extends Array<(node: ts.Node) => node is ts.Node>
185185
if (!oneOfTest) throw new Error('Tests are not provided')
186186
return oneOfTest.some(test => test(node))
187187
}
188+
189+
export const patchMethod = <T, K extends keyof T>(obj: T, method: K, overriden: (oldMethod: T[K]) => T[K]) => {
190+
const oldValue = obj[method]
191+
Object.defineProperty(obj, method, {
192+
value: overriden(oldValue),
193+
})
194+
return () => {
195+
Object.defineProperty(obj, method, {
196+
value: oldValue,
197+
})
198+
}
199+
}

typescript/test/completions.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import { createRequire } from 'module'
1212
import { findChildContainingPosition } from '../src/utils'
1313
import handleCommand from '../src/specialCommands/handle'
1414
import _ from 'lodash'
15+
import decorateFormatFeatures from '../src/decorateFormatFeatures'
16+
17+
// TODO rename file to plugin.spec.ts or move other tests
1518

1619
const require = createRequire(import.meta.url)
1720
//@ts-ignore plugin expect it to set globallly
@@ -499,3 +502,30 @@ test('In Keyword Completions', () => {
499502
}
500503
`)
501504
})
505+
506+
test('Format ignore', () => {
507+
decorateFormatFeatures(languageService, { ...languageService }, defaultConfigFunc)
508+
const [pos] = newFileContents(/* ts */ `
509+
const a = {
510+
//@ts-format-ignore-region
511+
a: 1,
512+
a1: 2,
513+
//@ts-format-ignore-endregion
514+
b: 3,
515+
//@ts-format-ignore-line
516+
c: 4,
517+
}
518+
`)
519+
const edits = languageService.getFormattingEditsForRange(entrypoint, 0, files[entrypoint]!.length, {}).filter(x => !!x.newText)
520+
expect(edits).toMatchInlineSnapshot(/* json */ `
521+
[
522+
{
523+
"newText": " ",
524+
"span": {
525+
"length": 2,
526+
"start": 134,
527+
},
528+
},
529+
]
530+
`)
531+
})

0 commit comments

Comments
 (0)