UNPKG

prettierx

Version:

prettierX - a less opinionated fork of the Prettier code formatter

198 lines (172 loc) 5.45 kB
"use strict"; const fs = require("fs"); const path = require("path"); const readlines = require("n-readlines"); const { UndefinedParserError } = require("../common/errors"); const { getSupportInfo } = require("../main/support"); const normalizer = require("./options-normalizer"); const { resolveParser } = require("./parser"); const hiddenDefaults = { astFormat: "estree", printer: {}, originalText: undefined, locStart: null, locEnd: null, }; // Copy options and fill in default values. function normalize(options, opts = {}) { const rawOptions = { ...options }; const supportOptions = getSupportInfo({ plugins: options.plugins, showUnreleased: true, showDeprecated: true, }).options; const defaults = { ...hiddenDefaults, ...Object.fromEntries( supportOptions .filter((optionInfo) => optionInfo.default !== undefined) .map((option) => [option.name, option.default]) ), }; if (!rawOptions.parser) { if (!rawOptions.filepath) { const logger = opts.logger || console; logger.warn( "No parser and no filepath given, using 'babel' the parser now " + "but this will throw an error in the future. " + "Please specify a parser or a filepath so one can be inferred." ); rawOptions.parser = "babel"; } else { rawOptions.parser = inferParser(rawOptions.filepath, rawOptions.plugins); if (!rawOptions.parser) { throw new UndefinedParserError( `No parser could be inferred for file: ${rawOptions.filepath}` ); } } } const parser = resolveParser( normalizer.normalizeApiOptions( rawOptions, [supportOptions.find((x) => x.name === "parser")], { passThrough: true, logger: false } ) ); rawOptions.astFormat = parser.astFormat; rawOptions.locEnd = parser.locEnd; rawOptions.locStart = parser.locStart; const plugin = getPlugin(rawOptions); rawOptions.printer = plugin.printers[rawOptions.astFormat]; const pluginDefaults = Object.fromEntries( supportOptions .filter( (optionInfo) => optionInfo.pluginDefaults && optionInfo.pluginDefaults[plugin.name] !== undefined ) .map((optionInfo) => [ optionInfo.name, optionInfo.pluginDefaults[plugin.name], ]) ); const mixedDefaults = { ...defaults, ...pluginDefaults }; for (const [k, value] of Object.entries(mixedDefaults)) { if (rawOptions[k] === null || rawOptions[k] === undefined) { rawOptions[k] = value; } } if (rawOptions.parser === "json") { rawOptions.trailingComma = "none"; } return normalizer.normalizeApiOptions(rawOptions, supportOptions, { passThrough: Object.keys(hiddenDefaults), ...opts, }); } function getPlugin(options) { const { astFormat } = options; // TODO: test this with plugins /* istanbul ignore next */ if (!astFormat) { throw new Error("getPlugin() requires astFormat to be set"); } const printerPlugin = options.plugins.find( (plugin) => plugin.printers && plugin.printers[astFormat] ); // TODO: test this with plugins /* istanbul ignore next */ if (!printerPlugin) { throw new Error(`Couldn't find plugin for AST format "${astFormat}"`); } return printerPlugin; } function getInterpreter(filepath) { /* istanbul ignore next */ if (typeof filepath !== "string") { return ""; } let fd; try { fd = fs.openSync(filepath, "r"); } catch { // istanbul ignore next return ""; } try { const liner = new readlines(fd); const firstLine = liner.next().toString("utf8"); // #!/bin/env node, #!/usr/bin/env node const m1 = firstLine.match(/^#!\/(?:usr\/)?bin\/env\s+(\S+)/); if (m1) { return m1[1]; } // #!/bin/node, #!/usr/bin/node, #!/usr/local/bin/node const m2 = firstLine.match(/^#!\/(?:usr\/(?:local\/)?)?bin\/(\S+)/); if (m2) { return m2[1]; } return ""; } catch { // There are some weird cases where paths are missing, causing Jest // failures. It's unclear what these correspond to in the real world. /* istanbul ignore next */ return ""; } finally { try { // There are some weird cases where paths are missing, causing Jest // failures. It's unclear what these correspond to in the real world. fs.closeSync(fd); } catch { // nop } } } function inferParser(filepath, plugins) { const filename = path.basename(filepath).toLowerCase(); const languages = getSupportInfo({ plugins }).languages.filter( (language) => language.since !== null ); // If the file has no extension, we can try to infer the language from the // interpreter in the shebang line, if any; but since this requires FS access, // do it last. let language = languages.find( (language) => (language.extensions && language.extensions.some((extension) => filename.endsWith(extension) )) || (language.filenames && language.filenames.some((name) => name.toLowerCase() === filename)) ); if (!language && !filename.includes(".")) { const interpreter = getInterpreter(filepath); language = languages.find( (language) => language.interpreters && language.interpreters.includes(interpreter) ); } return language && language.parsers[0]; } module.exports = { normalize, hiddenDefaults, inferParser };