[译] Prettier 插件开发文档

微信扫一扫,分享到朋友圈

[译] Prettier 插件开发文档

本文翻译自:https://prettier.io/docs/en/plugins.html

Prettier的插件机制用于对新增语言实现格式化。Prettier目前自己实现的对所有语言的格式化都是基于插件API实现的。prettier的核心包内置了JavaScript以及其他web主流语言的格式化实现。对于其他语言的格式化支持你需要安装对应的插件。

使用插件

如果插件安装在prettier所在的同一个node modules目录中,插件会被自动加载。插件包的命名必须以“@prettier/plugin-”或“prettier-plugin-”或者“@ /prettier-plugin-”打头。

记得要被替换为一个其他的名字,更多信息可以参考 NPM scope.

当插件不能被自动发现的时候,你可以以下面的方式加载它:


cli的方式,通过–plugin 以及 –plugin-search-dir参数:

  prettier --write main.foo --plugin-search-dir=./dir-with-plugins --plugin=./foo-plugin

注意:你可以多次设置–plugin 或 –plugin-search-dir选项


或者API的方式,通过设置plugins 及 pluginSearchDirs的方式:

prettier.format("code",{

parser:"foo",

pluginSearchDirs:["./dir-with-plugins"],

plugins:["./foo-plugin"],

});

Prettier期望每一个pluginSearchDirs 都包含 node_modules 子目录,这样@prettier/plugin- , @
/prettier-plugin-* 以及 prettier-plugin-*会在node_modules文件夹中被查询。比如,pluginSearchDirs 可以是你的项目目录或者是全局的npm模块的位置。

如果 –plugin-search-dir/pluginSearchDirs 中的至少一个被提供了参数,就会停止自动从默认的目录加载插件(比如, prettier 下的 node_modules 目录)

官方插件



@prettier/plugin-php



@prettier/plugin-pug by @Shinigami92



@prettier/plugin-ruby



@prettier/plugin-swift



@prettier/plugin-xml

社区插件



prettier-plugin-apex by @dangmai



prettier-plugin-elm by @giCentre



prettier-plugin-java by @JHipster



prettier-plugin-kotlin by @Angry-Potato



prettier-plugin-package by @shellscape



prettier-plugin-packagejson by @matzkoh



prettier-plugin-pg by @benjie



prettier-plugin-properties by @eemeli



prettier-plugin-solidity by @mattiaerre



prettier-plugin-svelte by @UnwrittenFun



prettier-plugin-toml by @bd82



prettier-plugin-organize-imports by @simonhaenisch



prettier-plugin-pkg by @JounQin



prettier-plugin-sh by @JounQin

开发插件

Prettier的插件就是常规的JavaScript模块,有下面的5个导出项:



languages



parsers



printers



options



defaultOptions languages languages是你的插件即将为Prettier贡献的语言定义的数组。它可以包含在 prettier.getSupportInfo()中指定的所有字段。

数组中的每一项必须包含 name 以及 parsers。

exportconst languages =[

{

// The language name

name:"InterpretedDanceScript",

// Parsers that can parse this language.

// This can be built-in parsers, or parsers you have contributed via this plugin.

parsers:["dance-parse"],

},

];

parsers

Parsers负责将代码字符串转为AST(Abstract Syntax Tree)。parseres的key必须跟 languages数组里某一项的 parsers数组里的名字匹配。值包含一个parse函数,一个AST格式化的名字,两个位置提取函数 (locStart and locEnd)。

exportconst parsers ={

"dance-parse":{

parse,

// The name of the AST that

astFormat:"dance-ast",

hasPragma,

locStart,

locEnd,

preprocess,

},

};

parse 函数的格式为:

function parse(text: string, parsers: object, options: object): AST;

位置抽取函数 (locStart and locEnd) 返回一个给定AST节点的开始及结束位置:

function locStart(node: object): number;

(可选)pragma检测函数 (hasPragma) 应该返回代码字符串是否包含注释。

function hasPragma(text: string): boolean;

(可选)预处理(preprocess)函数可以在执行parse函数之前预处理输入文本。

function preprocess(text: string, options: object): string;

printers

Printers负责将所有的AST转化为一种Prettier的中间表示,也被称作:Doc。

printers的key值必须与上面的parsers中parser的 astFormat 相匹配(比如上面:dance-parse的astFormat值为:’dance-ast’)。值是一个包含一个 print函数的对象,所有的其他属性 (embed, preprocess, 等等)都是可选的。

exportconst printers ={

"dance-ast":{

print,

embed,

preprocess,

insertPragma,

canAttachComment,

isBlockComment,

printComment,

handleComments:{

ownLine,

endOfLine,

remaining,

},

},

};

打印过程

Prettier使用一个被称作Doc的中间表示,然后将该中间表示转化为一个字符串(依赖于 printWidth等的选项)。一个printer的工作是解析 parsers[ ].parse生成的AST并返回一个Doc。Doc使用 builder commands构建:

const { concat, join, line, ifBreak, group } = require("prettier").doc.builders;

打印的过程(将AST转为Doc)如下:


1.
preprocess(ast: AST, options: object): AST, 存在于Parsers中,如果配置了的话,将会被调用。会被传入parser生成的AST作为参数。被 preprocess 返回的AST将会被Prettier使用。如果 preprocess 没有被配置,parser返回的AST将会被直接使用。


2.
注释节点会被附加到AST上(可以参考 在一个printer中处理注释 来查看更多细节)


3.
Doc是从AST递归构造的。
1.
embed(path: FastPath, print, textToDoc, options: object): Doc | null 在每一个AST节点被会被调用。如果 embed 返回了一个Doc,该Doc就会被使用。


2.
如果 embed 没有被定义或者返回了一个falsy的值, print(path: FastPath, options: object, print): Doc会在每一个AST节点上被调用。

print

一个插件的printer大部分的工作都将发生在其 print 函数中,该函数的结构是:

functionprint(

// Path to the AST node to print

path:FastPath,

options:object,

// Recursively print a child node

print:(path:FastPath)=>Doc

):Doc;

print 函数被传入一个 path 对象,可以通过path.getValue()来获取AST中的节点信息。还被传递了一个持久化的 options 对象(其中包含全局的options,插件可能会改变该该配置项),以及一个 print 用来做递归调用。一个基本的print 函数可能像下面的代码所示:

const{ builders }=require("prettier").doc;

functionprint(path, options,print){

const node = path.getValue();

if(Array.isArray(node)){

return builders.concat(path.map(print));

}

return node.value;

}

可以看下 prettier-python’s printer 来了解一些可能的例子。

(可选) embed

embed 函数在插件需要打印一个嵌套在另一种语言中的语言的时候被调用。例如打印css-in-js或在Markdown中打印fenced代码块。其签名为:

function embed(

// Path to the current AST node

path:FastPath,

// Print a node with the current printer

print:(path:FastPath)=>Doc,

// Parse and print some text using a different parser.

// You should set `options.parser` to specify which parser to use.

textToDoc:(text:string, options:object)=>Doc,

// Current options

options:object

):Doc|null;

embed 函数的行为类似于 print ,除了它会被额外传递一个 textToDoc参数,该参数可以被用来使用不同的插件来渲染一个Doc。embed 函数返回一个Doc或者一个falsy值。如果一个falsy值被返回,载当前 path下 print 函数会被执行。如果返回了一个Doc,该Doc会在打印中被使用, print 将不会再被调用。

比如,一个具有嵌入JavaScript节点的插件可能具有以下 embed 函数:

function embed(path,print, textToDoc, options){

const node = path.getValue();

if(node.type ==="javascript"){

return textToDoc(node.javaScriptText,{...options, parser:"babel"});

}

returnfalse;

}

(可选)preprocess

preprocess函数可以在AST被传入到 print 函数中之前提前进入到parser的AST。

function preprocess(ast: AST, options: object): AST;

(可选) insertPragma

在 insertPragma 函数中使用 –insert-pragma 选项的时候,一个插件可以实现将一个注释代码插入到最终的结果字符串中。它的签名是:

function insertPragma(text: string): string;

在printer中处理注释

注释通常并不会作为一们语言的AST的一部分,这对prettier的打印器来说是一项挑战。一个Prettier的插件既可以在它自身的 print 函数中打印出注释,也可以依赖于Prettier的注释算法。

默认的,如果AST的顶层(根目录)拥有 comments 属性,Prettier就会假定该 comments 属性是包含有注释节点的数组。然后Prettier会使用提供的 parsers[ ].locStart/locEnd 函数来检查每一个注释应该依附的AST节点。然后在打印的过程中注释被附加到这些对应的节点上,修改AST,并从AST根目录中删除Comments属性。*Comment函数被用来调整Prettier的算法。一旦注释被附加到了AST上,Prettier会自动调用 printComment(path, options): Doc 函数,并将返回的doc插入到(期望的)正确的位置上。

(可选)printComment

当一个注释节点需要被打印时会被调用。签名为:

function printComment(

// Path to the current comment node

commentPath:FastPath,

// Current options

options:object

):Doc;

(可选)canAttachComment

function canAttachComment(node: AST): boolean;

该函数被用于判断是否能将一个注释节点附加到某一个AST节点上。默认的,会遍历所有的AST属性,来搜索可以附加注释的节点。该函数被用于阻止注释节点被附加到某一个特定的节点上。典型的实现如下:

function canAttachComment(node) {return node.type && node.type !== "comment";}

(可选)isBlockComment

function isBlockComment(node: AST): boolean;

判断AST节点是否是一个块注释节点。

(可选)handleComments

handleComments 对象包含三个可选的函数,每一个函数都具有下面的签名:

function(

// The AST node corresponding to the comment

comment: AST,

// The full source code text

text:string,

// The global options object

options:object,

// The AST

ast: AST,

// Whether this comment is the last comment

isLastComment:boolean

):boolean

这些函数用于覆盖Prettier的默认注释附加算法。

ownLine/endOfLine/remaining 被期望或者手动将注释附加到节点并返回true,或者返回false并让Prettier来附加注释。基于注释节点周围的文本,Prettier会调度:



ownLine 如果一个注释前面只有空格,后面有换行符



endOfLine 如果一个注释后面有一个换行,但前面有一些非空白



remaining 所有其他的情形

在Prettier调度的时候,Prettier将至少使用 enclosingNode, precedingNode, 或者 followingNode中的一个方法对每一个AST comment节点(如:新创建的属性)进行注释。这可以用来帮助插件做决策(当然,为了做出更复杂的决定,整个AST和原始文本也会被传递进来)。

手动附加注释 util.addTrailingComment/addLeadingComment/addDanglingComment
函数可以被用来手动的为AST节点添加注释。一个 ownLine 函数的例子可以确保注释不跟在“标点符号”节点(为演示目的而编写)后面,可能如下所示:

const{ util }=require("prettier");

function ownLine(comment, text, options, ast, isLastComment){

const{ precedingNode }= comment;

if(precedingNode && precedingNode.type ==="punctuation"){

util.addTrailingComment(precedingNode, comment);

returntrue;

}

returnfalse;

}

带有注释的节点应具有包含注释数组的comments属性。每一个注释都应该包含下面的属性:leading, trailing, printed。

上面的例子使用了 util.addTrailingComment,该方法会自动的设置comment.leading/trailing/printed 为适当的值,并将注释添加到AST节点的 comments 数组中。options options是一个包含插件支持的自定义选项的对象。一个可能的例子是:

options:{

openingBraceNewLine:{

type:"boolean",

category:"Global",

default:true,

description:"Move open brace for code blocks onto new line."

}

}

defaultOptions

如果您的插件要求Prettier的一些核心选项有不同的默认值,您可以在 defaultOptions 中指定它们:

defaultOptions:{

tabWidth:4

}

工具函数

来自Prettier core的 util 模块被认为是一个私有API,并不打算被插件消费。作为替代, util-shared 模块为插件提供了以下有限的工具函数集合:

type Quote='"'|"'";

type SkipOptions={ backwards?:boolean};

function getMaxContinuousCount(str:string, target:string): number;

function getStringWidth(text:string): number;

function getAlignmentSize(value:string, tabWidth: number, startIndex?: number): number;

function getIndentSize(value:string, tabWidth: number): number;

function skip(chars:string|RegExp):(text:string, index: number |false, opts?:SkipOptions)=> number |false;

function skipWhitespace(text:string, index: number |false, opts?:SkipOptions): number |false;

function skipSpaces(text:string, index: number |false, opts?:SkipOptions): number |false;

function skipToLineEnd(text:string, index: number |false, opts?:SkipOptions): number |false;

function skipEverythingButNewLine(text:string, index: number |false, opts?:SkipOptions): number |false;

function skipInlineComment(text:string, index: number |false): number |false;

function skipTrailingComment(text:string, index: number |false): number |false;

function skipNewline(text:string, index: number |false, opts?:SkipOptions): number |false;

function hasNewline(text:string, index: number, opts?:SkipOptions):boolean;

function hasNewlineInRange(text:string, start: number,end: number):boolean;

function hasSpaces(text:string, index: number, opts?:SkipOptions):boolean;

function makeString(rawContent:string, enclosingQuote:Quote, unescapeUnnecessaryEscapes?:boolean):string;

function getNextNonSpaceNonCommentCharacterIndex<N>(text:string, node: N, locEnd:(node: N)=> number): number |false;

function isNextLineEmptyAfterIndex(text:string, index: number):boolean;

function isNextLineEmpty<N>(text:string, node: N, locEnd:(node: N)=> number):boolean;

function isPreviousLineEmpty<N>(text:string, node: N, locStart:(node: N)=> number):boolean;

教程


How to write a plugin for Prettier:教你如何为TOML编写一个非常基本的Prettier插件 或者访问这个地址

测试插件

由于插件可以使用相对路径进行解析,因此在处理一个插件时可以这样做:

const prettier =require("prettier");const code ="(add 1 2)";

prettier.format(code,{

parser:"lisp",

plugins:["."],}

);

上面的代码会加载位于当前工作目录下的一个插件。

微信扫一扫,分享到朋友圈

[译] Prettier 插件开发文档

如何使用 ThinkJS 优雅的编写 RESTful API

上一篇

mybatis如何防止SQL注入?

下一篇

你也可能喜欢

[译] Prettier 插件开发文档

长按储存图像,分享给朋友