Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ All notable changes to the "prettier-vscode" extension will be documented in thi

## [Unreleased]

- Added [`parser`](https://prettier.io/docs/options.html#parser) setting. The setting can be used in conjunction with language-specific settings to override the parser for specific VSCode languages. Alternatively, setting `prettier.parser: "vscode"` will derive the parser from the active VSCode language mode.

## [12.4.0]

- **Security**: Fixed config resolution in untrusted workspaces to prevent JavaScript config files (`.prettierrc.js`, `prettier.config.js`, etc.) from being executed. Previously, even when workspace trust was enforced for module resolution, Prettier's config resolution could still `require()`/`import()` JS config files, allowing arbitrary code execution. Reported by Hector Ruiz Ruiz.
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ prettier.bracketSameLine
prettier.jsxBracketSameLine
prettier.jsxSingleQuote
prettier.printWidth
prettier.parser
prettier.proseWrap
prettier.quoteProps
prettier.requirePragma
Expand Down Expand Up @@ -320,6 +321,16 @@ To configure language-specific settings, use the `[language]` syntax in your VS

This feature is particularly useful when working in multi-language projects or when different languages have different formatting conventions. Language-specific settings will override the global Prettier settings when formatting files of that language type.

You can also set `prettier.parser` in language-specific overrides to force a parser, or use `prettier.parser: "vscode"` to derive the parser from the active VS Code language mode. For example:

```json
{
"[git-commit]": {
"prettier.parser": "markdown"
}
}
```

### Extension Settings

These settings are specific to VS Code and need to be set in the VS Code settings file. See the [documentation](https://code.visualstudio.com/docs/getstarted/settings) for how to do that.
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,11 @@
"markdownDescription": "%ext.config.proseWrap%",
"scope": "language-overridable"
},
"prettier.parser": {
"type": "string",
"markdownDescription": "%ext.config.parser%",
"scope": "language-overridable"
},
"prettier.arrowParens": {
"type": "string",
"enum": [
Expand Down
2 changes: 1 addition & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"ext.config.jsxSingleQuote": "Use single quotes instead of double quotes in JSX.",
"ext.config.packageManager": "The package manager you use to install node modules.",
"ext.config.packageManagerDeprecation": "Package manager is now automatically detected by VS Code. This setting is no longer used.",
"ext.config.parser": "Override the parser. You shouldn't have to change this setting.",
"ext.config.parser": "Explicitly specify the Prettier parser to use. Set to `vscode` to derive the parser from the active VS Code language mode.",
"ext.config.parserDeprecationMessage": "This setting is no longer supported. Use a prettier configuration file instead.",
"ext.config.prettierPath": "Path to the `prettier` module, eg: `./node_modules/prettier`.",
"ext.config.printWidth": "Fit code within this line limit.",
Expand Down
2 changes: 1 addition & 1 deletion package.nls.zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"ext.config.jsxSingleQuote": "JSX 中使用单引号而不是双引号。",
"ext.config.packageManager": "用于安装 node modules 的包管理器。",
"ext.config.packageManagerDeprecation": "包管理器现在由 VS Code 自动检测。此设置已不再使用。",
"ext.config.parser": "覆盖解析器。通常不需要更改此设置。",
"ext.config.parser": "显式指定要使用的 Prettier 解析器。设置为 `vscode` 以根据当前 VS Code 语言模式确定解析器。",
"ext.config.parserDeprecationMessage": "此设置已不再支持。请改用 Prettier 配置文件。",
"ext.config.prettierPath": "`prettier` 包路径,如 `./node_modules/prettier`。",
"ext.config.printWidth": "每行代码的长度限制。",
Expand Down
2 changes: 1 addition & 1 deletion package.nls.zh-tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"ext.config.jsxSingleQuote": "在 JSX 中使用單引號而不是雙引號。",
"ext.config.packageManager": "你用於安裝 node modules 的套件管理器。",
"ext.config.packageManagerDeprecation": "套件管理器目前會經由 VS Code 進行偵測。這個設定已不再使用。",
"ext.config.parser": "覆寫解析器。你不應變更這個設定。",
"ext.config.parser": "明確指定要使用的 Prettier 解析器。設定為 `vscode` 以根據目前 VS Code 語言模式決定解析器。",
"ext.config.parserDeprecationMessage": "這個設定已不再支援。請改用 prettier 組態檔。",
"ext.config.prettierPath": "`prettier` 模組的路徑,如 `./node_modules/prettier`。",
"ext.config.printWidth": "讓程式碼的每一列符合這個寬度限制。",
Expand Down
109 changes: 101 additions & 8 deletions src/ModuleResolverNode.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import type * as PrettierTypes from "prettier";
import * as semver from "semver";
Expand Down Expand Up @@ -34,6 +35,19 @@ import {
import { PrettierDynamicInstance } from "./PrettierDynamicInstance.js";

const minPrettierVersion = "1.13.0";
let emptyConfigPath: string | undefined;

async function getEmptyConfigPath(): Promise<string> {
if (emptyConfigPath) {
return emptyConfigPath;
}
const tempDir = await fs.promises.mkdtemp(
path.join(os.tmpdir(), "prettier-vscode-"),
);
emptyConfigPath = path.join(tempDir, "empty.prettierrc.json");
await fs.promises.writeFile(emptyConfigPath, "{}", { encoding: "utf8" });
return emptyConfigPath;
}

export type PrettierNodeModule = typeof PrettierTypes;

Expand Down Expand Up @@ -415,6 +429,31 @@ export class ModuleResolver implements ModuleResolverInterface {
return null;
}

const configSearchRoot = await findUp(
async (dir: string) => {
if (
await pathExists(
path.join(dir, ".do-not-use-prettier-vscode-root"),
)
) {
return dir;
}
return undefined;
},
{ cwd: path.dirname(fileName), type: "directory" },
);

const isWithinSearchRoot = (candidatePath: string) => {
if (!configSearchRoot) {
return true;
}
const relative = path.relative(configSearchRoot, candidatePath);
return (
relative === "" ||
(!relative.startsWith("..") && !path.isAbsolute(relative))
);
};

let configPath: string | undefined;
try {
configPath =
Expand Down Expand Up @@ -445,21 +484,75 @@ export class ModuleResolver implements ModuleResolverInterface {
? getWorkspaceRelativePath(fileName, vscodeConfig.configPath)
: undefined;

let limitConfigSearch = false;
if (!customConfigPath && configSearchRoot) {
if (!configPath) {
limitConfigSearch = true;
} else if (!isWithinSearchRoot(configPath)) {
this.loggingService.logInfo(
`Ignoring config file outside search root: ${configPath}`,
);
configPath = undefined;
limitConfigSearch = true;
}
}

// Log if a custom config path is specified in VS Code settings
if (customConfigPath) {
this.loggingService.logInfo(
`Using custom config path from settings: ${customConfigPath}`,
);
}

const resolveConfigOptions: PrettierResolveConfigOptions = {
config: customConfigPath ?? configPath,
editorconfig: vscodeConfig.useEditorConfig,
};
resolvedConfig = await prettierInstance.resolveConfig(
fileName,
resolveConfigOptions,
);
if (customConfigPath || configPath) {
const resolveConfigOptions: PrettierResolveConfigOptions = {
config: customConfigPath ?? configPath,
editorconfig: vscodeConfig.useEditorConfig,
};
resolvedConfig = await prettierInstance.resolveConfig(
fileName,
resolveConfigOptions,
);
} else if (limitConfigSearch && vscodeConfig.useEditorConfig) {
const editorConfigPath = await findUp(
async (dir: string) => {
const editorConfigCandidate = path.join(dir, ".editorconfig");
if (await pathExists(editorConfigCandidate)) {
return editorConfigCandidate;
}
if (
await pathExists(
path.join(dir, ".do-not-use-prettier-vscode-root"),
)
) {
return FIND_UP_STOP;
}
return undefined;
},
{ cwd: path.dirname(fileName) },
);

if (editorConfigPath) {
const resolveConfigOptions: PrettierResolveConfigOptions = {
config: await getEmptyConfigPath(),
editorconfig: true,
};
resolvedConfig = await prettierInstance.resolveConfig(
fileName,
resolveConfigOptions,
);
} else {
resolvedConfig = null;
}
} else {
const resolveConfigOptions: PrettierResolveConfigOptions = {
editorconfig: vscodeConfig.useEditorConfig,
};
resolvedConfig = await prettierInstance.resolveConfig(
fileName,
resolveConfigOptions,
);
}
} catch (error) {
this.loggingService.logError(INVALID_PRETTIER_CONFIG, error);
return "error";
Expand Down
97 changes: 79 additions & 18 deletions src/PrettierEditService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -599,24 +599,67 @@ export default class PrettierEditService implements Disposable {
return;
}

const parserSetting =
typeof vscodeConfig.parser === "string"
? vscodeConfig.parser.trim()
: undefined;
const parserSettingIsVSCode =
parserSetting?.toLowerCase() === "vscode";

let parserSettingWasExplicit = false;
let parser: PrettierBuiltInParserName | string | undefined;
if (fileInfo && fileInfo.inferredParser) {
parser = fileInfo.inferredParser;
} else if (resolvedConfig && resolvedConfig.parser) {
// Parser specified in config (e.g., via overrides for custom extensions)
parser = resolvedConfig.parser as string;
} else if (languageId !== "plaintext") {
// Don't attempt VS Code language for plaintext because we never have
// a formatter for plaintext and most likely the reason for this is
// somebody has registered a custom file extension without properly
// configuring the parser in their prettier config.
this.loggingService.logWarning(
`Parser not inferred, trying VS Code language.`,

let supportInfoLanguages:
| Awaited<ReturnType<typeof prettierInstance.getSupportInfo>>["languages"]
| undefined;
const getSupportInfoLanguages = async () => {
if (!supportInfoLanguages) {
const { languages } = await prettierInstance.getSupportInfo({
plugins: resolvedPlugins,
});
supportInfoLanguages = languages;
}
return supportInfoLanguages;
};

if (parserSetting) {
parserSettingWasExplicit = true;
if (parserSettingIsVSCode) {
if (languageId !== "plaintext") {
const languages = await getSupportInfoLanguages();
parser = getParserFromLanguageId(languages, uri, languageId);
} else {
this.loggingService.logWarning(
"Parser set to 'vscode' but language is plaintext; no parser available.",
);
}
} else {
parser = parserSetting;
}

this.loggingService.logInfo(
"Using parser from VS Code settings:",
parser ?? parserSetting,
);
const { languages } = await prettierInstance.getSupportInfo({
plugins: resolvedPlugins,
});
parser = getParserFromLanguageId(languages, uri, languageId);
}

if (!parser && !parserSettingWasExplicit) {
if (fileInfo && fileInfo.inferredParser) {
parser = fileInfo.inferredParser;
} else if (resolvedConfig && resolvedConfig.parser) {
// Parser specified in config (e.g., via overrides for custom extensions)
parser = resolvedConfig.parser as string;
} else if (languageId !== "plaintext") {
// Don't attempt VS Code language for plaintext because we never have
// a formatter for plaintext and most likely the reason for this is
// somebody has registered a custom file extension without properly
// configuring the parser in their prettier config.
this.loggingService.logWarning(
`Parser not inferred, trying VS Code language.`,
);
const languages = await getSupportInfoLanguages();
parser = getParserFromLanguageId(languages, uri, languageId);
}
}

if (!parser) {
Expand All @@ -627,13 +670,22 @@ export default class PrettierEditService implements Disposable {
return;
}

const resolvedConfigForFormatting =
parserSettingWasExplicit && resolvedConfig
? (() => {
const { parser: _parser, ...rest } = resolvedConfig;
return rest;
})()
: resolvedConfig;

const prettierOptions = this.getPrettierOptions(
fileName,
parser as PrettierBuiltInParserName,
vscodeConfig,
resolvedConfig,
resolvedConfigForFormatting,
options,
resolvedPlugins,
parserSettingWasExplicit,
);

this.loggingService.logInfo("Prettier Options:", prettierOptions);
Expand Down Expand Up @@ -661,6 +713,7 @@ export default class PrettierEditService implements Disposable {
configOptions: PrettierOptions | null,
extensionFormattingOptions: ExtensionFormattingOptions,
resolvedPlugins: (string | PrettierPlugin)[],
parserSettingWasExplicit: boolean,
): Partial<PrettierOptions> {
const fallbackToVSCodeConfig = configOptions === null;

Expand Down Expand Up @@ -710,6 +763,14 @@ export default class PrettierEditService implements Disposable {
};
}

const configOptionsToApply =
parserSettingWasExplicit && configOptions
? (() => {
const { parser: _parser, ...rest } = configOptions;
return rest;
})()
: configOptions;

const options: PrettierOptions = {
...(fallbackToVSCodeConfig ? vsOpts : {}),
...{
Expand All @@ -718,7 +779,7 @@ export default class PrettierEditService implements Disposable {
parser: parser as PrettierBuiltInParserName,
},
...(rangeFormattingOptions || {}),
...(configOptions || {}),
...(configOptionsToApply || {}),
// Pass resolved plugin paths for Prettier to import
...(resolvedPlugins.length > 0 ? { plugins: resolvedPlugins } : {}),
};
Expand Down
Loading