/
index.ts
155 lines (139 loc) · 5.07 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import type { ServicePluginInstance, ServicePlugin, ServiceEnvironment } from '@volar/language-service';
import type { Options, ResolveConfigOptions } from 'prettier';
import { URI } from 'vscode-uri';
export function create(
options: {
/**
* Languages to be formatted by prettier.
*
* @default
* ['html', 'css', 'scss', 'typescript', 'javascript']
*/
languages?: string[];
html?: {
/**
* Preprocessing to break "contents" from "HTML tags".
* This will prevent HTML closing tags, and opening tags without attributes
* from breaking into a blank `>` or `<` on a new line.
*/
breakContentsFromTags?: boolean;
};
/**
* Do not use settings from VSCode's `editor.tabSize` and temporary tabSize on status bar
*
* @see https://github.com/volarjs/services/issues/5
*/
ignoreIdeOptions?: boolean;
/**
* Determine if IDE options should be used as a fallback if there's no Prettier specific settings in the workspace
*/
useIdeOptionsFallback?: boolean;
/**
* Additional options to pass to Prettier
* This is useful, for instance, to add specific plugins you need.
*/
additionalOptions?: (resolvedConfig: Options) => Options | Promise<Options>;
/**
* Options to use when resolving the Prettier config
*/
resolveConfigOptions?: ResolveConfigOptions;
/**
* Prettier instance to use. If undefined, Prettier will be imported through a normal `import('prettier')`.
* This property is useful whenever you want to load a specific instance of Prettier (for instance, loading the Prettier version installed in the user's project)
*/
prettier?: typeof import('prettier') | undefined;
getPrettier?: (serviceEnv: ServiceEnvironment) => typeof import('prettier') | undefined,
/**
* If true, the plugin will not throw an error if it can't load Prettier either through the `prettier`, or `getPrettier` properties or through a normal `import('prettier')`.
*/
allowImportError?: boolean;
} = {},
getPrettierConfig = async (filePath: string, prettier: typeof import('prettier'), config?: ResolveConfigOptions) => {
return await prettier.resolveConfig(filePath, config) ?? {};
},
): ServicePlugin {
return {
name: 'prettier',
create(context): ServicePluginInstance {
const languages = options.languages ?? ['html', 'css', 'scss', 'typescript', 'javascript'];
let _prettier = options.prettier;
try {
if (!_prettier) {
if (options.getPrettier) {
_prettier = options.getPrettier(context.env);
} else {
_prettier = require('prettier');
}
}
} catch (error) {
if (!options.allowImportError) {
throw new Error("Could not load Prettier: ");
};
}
if (!_prettier) {
return {};
}
const prettier = _prettier;
return {
async provideDocumentFormattingEdits(document, _, formatOptions) {
if (!languages.includes(document.languageId)) {
return;
}
const filePath = URI.parse(document.uri).fsPath;
const fileInfo = await prettier.getFileInfo(filePath, { ignorePath: '.prettierignore', resolveConfig: false });
if (fileInfo.ignored) {
return;
}
const filePrettierOptions = await getPrettierConfig(
filePath,
prettier,
options.resolveConfigOptions
);
const editorPrettierOptions = await context.env.getConfiguration?.('prettier', document.uri);
const ideFormattingOptions =
formatOptions !== undefined && options.useIdeOptionsFallback // We need to check for options existing here because some editors might not have it
? {
tabWidth: formatOptions.tabSize,
useTabs: !formatOptions.insertSpaces,
}
: {};
// Return a config with the following cascade:
// - Prettier config file should always win if it exists, if it doesn't:
// - Prettier config from the VS Code extension is used, if it doesn't exist:
// - Use the editor's basic configuration settings
const prettierOptions = returnObjectIfHasKeys(filePrettierOptions) || returnObjectIfHasKeys(editorPrettierOptions) || ideFormattingOptions;
const currentPrettierConfig: Options = {
...(options.additionalOptions
? await options.additionalOptions(prettierOptions)
: prettierOptions),
filepath: filePath,
};
if (!options.ignoreIdeOptions) {
currentPrettierConfig.useTabs = !formatOptions.insertSpaces;
currentPrettierConfig.tabWidth = formatOptions.tabSize;
}
const fullText = document.getText();
let oldText = fullText;
const isHTML = document.languageId === "html";
if (isHTML && options.html?.breakContentsFromTags) {
oldText = oldText
.replace(/(<[a-z][^>]*>)([^ \n])/gi, "$1 $2")
.replace(/([^ \n])(<\/[a-z][a-z0-9\t\n\r -]*>)/gi, "$1 $2");
}
return [{
newText: await prettier.format(oldText, currentPrettierConfig),
range: {
start: document.positionAt(0),
end: document.positionAt(fullText.length),
},
}];
},
};
},
};
}
function returnObjectIfHasKeys<T>(obj: T | undefined): T | undefined {
if (Object.keys(obj || {}).length > 0) {
return obj;
}
}