Skip to content

Commit

Permalink
Add support for global prettier plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
esamattis committed Mar 21, 2019
1 parent fd6fdfc commit 7ed1576
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 41 deletions.
25 changes: 25 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -240,6 +240,7 @@
"prettier-stylelint": "^0.4.2",
"prettier-tslint": "^0.4.2",
"read-pkg-up": "^4.0.0",
"requireg": "^0.2.1",
"resolve": "^1.10.0",
"typescript": "^2.9.2"
}
Expand Down
40 changes: 33 additions & 7 deletions src/PrettierEditProvider.ts
Expand Up @@ -10,7 +10,12 @@ import {
} from 'vscode';

import { safeExecution, addToOutput, setUsedModule } from './errorHandler';
import { getParsersFromLanguageId, getConfig, supportsLanguage } from './utils';
import {
getParsersFromLanguageId,
getConfig,
supportsLanguage,
requireGlobalPrettier,
} from './utils';
import { requireLocalPkg } from './requirePkg';

import {
Expand Down Expand Up @@ -83,6 +88,27 @@ function mergeConfig(
)
: Object.assign(vscodeConfig, prettierConfig, additionalConfig);
}

function getPrettierForFile(fileName: string): Prettier {
// First try to use the local prettier for this file
const local = requireLocalPkg<Prettier>(fileName, 'prettier');
if (local) {
console.log('Formatting with local prettier');
return local;
}

// Fallback to global if no local is found
const global = requireGlobalPrettier();
if (global) {
console.log('Formatting with global prettier');
return global;
}

// Finally use the bundled one if all else fail
console.log('Formatting with bundled prettier');
return bundledPrettier;
}

/**
* Format the given text with user's configuration.
* @param text Text to format
Expand All @@ -95,7 +121,7 @@ async function format(
customOptions: Partial<PrettierConfig>
): Promise<string> {
const vscodeConfig: PrettierVSCodeConfig = getConfig(uri);
const localPrettier = requireLocalPkg(fileName, 'prettier') as Prettier;
const prettierInstance = getPrettierForFile(fileName);

// This has to stay, as it allows to skip in sub workspaceFolders. Sadly noop.
// wf1 (with "lang") -> glob: "wf1/**"
Expand All @@ -104,7 +130,7 @@ async function format(
return text;
}

if (!supportsLanguage(languageId, localPrettier)) {
if (!supportsLanguage(languageId, prettierInstance)) {
window.showErrorMessage(
`Prettier does not support "${languageId}". Maybe a plugin is missing from the workspace?`
);
Expand All @@ -113,7 +139,7 @@ async function format(

const dynamicParsers = getParsersFromLanguageId(
languageId,
localPrettier,
prettierInstance,
isUntitled ? undefined : fileName
);
let useBundled = false;
Expand Down Expand Up @@ -232,7 +258,7 @@ async function format(
() => {
const warningMessage =
`prettier@${
localPrettier.version
prettierInstance.version
} doesn't support ${languageId}. ` +
`Falling back to bundled prettier@${
bundledPrettier.version
Expand All @@ -249,10 +275,10 @@ async function format(
);
}

setUsedModule('prettier', localPrettier.version, false);
setUsedModule('prettier', prettierInstance.version, false);

return safeExecution(
() => localPrettier.format(text, prettierOptions),
() => prettierInstance.format(text, prettierOptions),
text,
fileName
);
Expand Down
7 changes: 3 additions & 4 deletions src/requirePkg.ts
Expand Up @@ -36,18 +36,17 @@ function findPkg(fspath: string, pkgName: string): string | undefined {
* @param {string} pkgName package's name to require
* @returns module
*/
function requireLocalPkg(fspath: string, pkgName: string): any {
function requireLocalPkg<T>(fspath: string, pkgName: string): T | undefined {
const modulePath = findPkg(fspath, pkgName);
console.log('Found module path', modulePath);
if (modulePath !== void 0) {
try {
return require(modulePath);
} catch (e) {
addToOutput(
`Failed to load ${pkgName} from ${modulePath}. Using bundled`
`Failed to load ${pkgName} from ${modulePath}. Using bundled or global`
);
}
}

return require(pkgName);
}
export { requireLocalPkg };
103 changes: 73 additions & 30 deletions src/utils.ts
Expand Up @@ -8,8 +8,18 @@ import {
} from './types.d';
import { requireLocalPkg } from './requirePkg';

const requireGlobal = require('requireg');
const bundledPrettier = require('prettier') as Prettier;

/**
* Require global prettier or undefined if none is installed
*/
export function requireGlobalPrettier(): Prettier | undefined {
try {
return requireGlobal('prettier', true);
} catch (error) {}
}

export function getConfig(uri?: Uri): PrettierVSCodeConfig {
return workspace.getConfiguration('prettier', uri) as any;
}
Expand All @@ -35,38 +45,66 @@ export function getParsersFromLanguageId(
return language.parsers;
}

/**
* Type Guard function for filtering empty values out of arrays.
*
* Usage: arr.filter(notEmpty)
*/
export function notEmpty<TValue>(
value: TValue | null | undefined
): value is TValue {
return value !== null && value !== undefined;
}

/**
* Find all enabled languages from all available prettiers: local, global and
* bundled.
*/
export function allEnabledLanguages(): string[] {
if (!workspace.workspaceFolders) {
return getSupportLanguages().reduce(
(ids, language) => [...ids, ...(language.vscodeLanguageIds || [])],
[] as string[]
);
let prettierInstances: Prettier[] = [bundledPrettier];

const globalPrettier = requireGlobalPrettier();
if (globalPrettier) {
console.log('Has global Prettier');
prettierInstances = prettierInstances.concat(globalPrettier);
} else {
console.log('No global Prettier');
}

return workspace.workspaceFolders.reduce(
(ids, workspaceFolder) => {
const workspacePrettier = requireLocalPkg(
workspaceFolder.uri.fsPath,
'prettier'
) as Prettier;

const newLanguages: string[] = [];

for (const language of getSupportLanguages(workspacePrettier)) {
if (!language.vscodeLanguageIds) {
continue;
}
for (const id of language.vscodeLanguageIds) {
if (!ids.includes(id)) {
newLanguages.push(id);
}
}
if (workspace.workspaceFolders) {
// Each workspace can have own local prettier
const localPrettiers = workspace.workspaceFolders
.map(workspaceFolder =>
requireLocalPkg<Prettier>(
workspaceFolder.uri.fsPath,
'prettier'
)
)
.filter(notEmpty);

prettierInstances = prettierInstances.concat(localPrettiers);
}

return getUniqueSupportedLanguages(prettierInstances);
}

function getUniqueSupportedLanguages(prettierInstances: Prettier[]): string[] {
const languages = new Set<string>();

for (const prettier of prettierInstances) {
for (const language of getSupportLanguages(prettier)) {
if (!language.vscodeLanguageIds) {
continue;
}

return [...ids, ...newLanguages];
},
[] as string[]
);
for (const id of language.vscodeLanguageIds) {
languages.add(id);
}
}
}

console.log('LANGS', prettierInstances.length, Array.from(languages));
return Array.from(languages);
}

export function rangeSupportedLanguages(): string[] {
Expand All @@ -80,11 +118,16 @@ export function rangeSupportedLanguages(): string[] {
];
}

export function getGroup(group: string): PrettierSupportInfo['languages'] {
return getSupportLanguages().filter(language => language.group === group);
export function getGroup(
prettier: Prettier,
group: string
): PrettierSupportInfo['languages'] {
return getSupportLanguages(prettier).filter(
language => language.group === group
);
}

function getSupportLanguages(prettierInstance: Prettier = bundledPrettier) {
function getSupportLanguages(prettierInstance: Prettier) {
return prettierInstance.getSupportInfo(prettierInstance.version).languages;
}

Expand Down

0 comments on commit 7ed1576

Please sign in to comment.