Contribute to this guide

guide代码风格

CKEditor 5 开发环境 已启用 ESLint 作为预提交钩子和 CI。这意味着代码风格问题会自动检测。此外,每个存储库中都有 .editorconfig 文件,以自动调整您的 IDE 设置(如果配置为读取它们)。

以下是对这些规则的简要概述。

# 通用

LF 用于行尾。绝不使用 CRLF。

建议的最大行长为 120 个字符。它不能超过 140 个字符。

# 空白

没有尾随空格。空行不应包含任何空格。

在括号内运算符前后的空白

function foo( a, b, c, d, e ) {
    if ( a > b ) {
        c = ( d + e ) * 2;
    }
}

foo( bar() );

空括号没有空白

const a = () => {
    // Statements.
    // ...
};

a();

冒号和分号之前没有空白

let a, b;

a( 1, 2, 3 );

for ( const i = 0; i < 100; i++ ) {
    // Statements.
    // ...
}

# 缩进

使用制表符缩进,包括代码和注释。绝不使用空格。

如果要使代码可读,请在 IDE 中将制表符设置为4 个空格

class Bar {
    a() {
        while ( b in a ) {
            if ( b == c ) {
                // Statements.
                // ...
            }
        }
    }
}

多行条件。每行使用一个制表符

if (
    some != really.complex &&
    condition || with &&
    ( multiple == lines )
) {
    // Statements.
    // ...
}

while (
    some != really.complex &&
    condition || with &&
    ( multiple == lines )
) {
    // Statements.
    // ...
}

我们尽力避免复杂的条件。作为经验法则,我们首先建议找到一种方法将复杂性从条件中移出,例如,移到一个单独的函数中,并为这种条件中的每个“句子”进行提前返回。

然而,过度做也不好,有时这样的条件可以完美地阅读(这是这里的最终目标)。

# 大括号

大括号与头部语句在同一行开始,并与其对齐结束

function a() {
    // Statements.
    // ...
}

if ( a ) {
    // Statements.
    // ...
} else if ( b ) {
    // Statements.
    // ...
} else {
    // Statements.
    // ...
}

try {
    // Statements.
    // ...
} catch ( e ) {
    // Statements.
    // ...
}

# 空行

代码应该像书一样阅读,因此在代码的“段落”之间放置空行。这是一个开放的和上下文相关的规则,但一些建议是将以下部分分开

  • 变量、类和函数声明,
  • if()for() 和类似块,
  • 算法的步骤,
  • return 语句,
  • 注释部分(注释应该用空行开头,但如果它们本身是“段落”,也应该用空行结尾),
  • 等等。

示例

class Foo extends Plugin {
    constructor( editor ) {
        super( editor );

        /**
        * Some documentation...
        */
        this.foo = new Foo();

        /**
        * Some documentation...
        */
        this.isBar = false;
    }

    method( bar ) {
        const editor = this.editor;
        const selection = editor.model.document.selection;

        for ( const range of selection.getRanges() ) {
            const position = range.start;

            if ( !position ) {
                return false;
            }

            // At this stage this and this need to happen.
            // We considered doing this differently, but it has its shortcomings.
            // Refer to the tests and issue #3456 to learn more.

            const result = editor.model.checkSomething( position );

            if ( result ) {
                return true;
            }
        }

        return true;
    }

    performAlgorithm() {
        // 1. Do things A and B.
        this.a();
        this.b();

        // 2. Check C.
        if ( c() ) {
            d();
        }

        // 3. Check whether we are fine.
        const areWeFine = 1 || 2 || 3;

        this.check( areWeFine );

        // 4. Finalize.
        this.finalize( areWeFine );

        return areWeFine;
    }
}

# 多行语句和调用

每当有多行函数调用时

  • 将第一个参数放在新行中。
  • 将每个参数放在单独的行中,缩进一个制表符。
  • 将最后一个右括号放在新行中,与调用的开头位于同一缩进级别。

示例

const myObj = new MyClass(
    'Some long parameters',
    'To make this',
    'Multi line'
);

fooBar(
    () => {
        // Statements.
        // ...
    }
);

fooBar(
    new MyClass(
        'Some long parameters',
        'To make this',
        'Multi line'
    )
);

fooBar(
    'A very long string',
    () => {
        // Some kind of a callback.
        // ...
    },
    5,
    new MyClass(
        'It looks well',
        paramA,
        paramB,
        new ShortClass( 2, 3, 5 ),
        'Even when nested'
    )
);

这些示例只是展示了如何构造这样的函数调用。但是,最好避免它们。

通常建议避免使用接受超过 3 个参数的函数。相反,最好将它们包装在对象中,以便所有参数都可以命名。

还建议将此类长语句拆分为多个较短的语句,例如,通过将一些较长的参数提取到单独的变量中。

# 字符串

使用单引号

const a = 'I\'m an example for quotes';

长字符串可以用加号 (+) 连接

const html =
    'Line 1\n' +
    'Line 2\n' +
    'Line 3';

或者你可以使用模板字符串。在这种情况下,第二行和第三行将缩进

const html =
    `Line 1
    Line 2
    Line 3`;

HTML 字符串应使用缩进以提高可读性

const html =
    `<p>
        <span>${ a }</span>
    </p>`;

# 注释

  • 注释总是以空行开头
  • 注释以大写第一个字母开头,并在末尾需要一个句点(因为它们是句子)。
  • 注释的文本开头必须有一个空格,紧跟在注释标记之后。

块注释 (/** ... */) 用于文档目的。星号与空格对齐

/**
 * Documentation for the following method.
 *
 * @returns {Object} Something.
 */
someMethod() {
    // Statements.
    // ...
}

所有其他注释使用行注释 (//)

// A comment about the following statement.
foo();

// Multiple line comments
// go through several
// line comments as well.

与工单或问题相关的注释不应完全描述整个问题。相反,应该使用简短的描述,以及括号中的工单号

// Do this otherwise because of a Safari bug. (#123)
foo();

# 代码风格检查

CKEditor 5 开发环境使用 ESLintstylelint

一些有用的链接

避免在现有代码上使用自动代码格式化程序。自动格式化您正在编辑的代码是可以的,但您不应该更改已编写代码的格式,以免污染您的 PR。您也不应该仅仅依赖于自动更正。

# 可见性级别

每个类属性(包括方法、符号、获取器或设置器)可以是公共的、受保护的或私有的。默认可见性为公共的,因此您不应该记录属性是公共的——没有必要这样做。

私有属性适用其他规则

  • 在类原型(或以任何其他方式)中公开的私有和受保护属性的名称应以下划线为前缀。
  • 在记录未添加到类原型(或以任何其他方式公开)的私有变量时,应使用// 注释,并且不需要使用@private
  • 符号属性(如this[ Symbol( 'symbolName' ) ])应记录为@property {Type} _symbolName

示例

class Foo {
    /**
    * The constructor (public, as its visibility isn't defined).
    */
    constructor() {
        /**
        * Public property.
        */
        this.foo = 1;

        /**
        * Protected property.
        *
        * @protected
        */
        this._bar = 1;

        /**
        * @private
        * @property {Number} _bom
        */
        this[ Symbol( 'bom' ) ] = 1;
    }

    /**
    * @private
    */
    _somePrivateMethod() {}
}

// Some private helper.
//
// @returns {Number}
function doSomething() {
    return 1;
}

# 可访问性

下表显示了属性的可访问性

  子类 世界
@public
@protected
@private

(是——可访问,否——不可访问)

例如,受保护属性可以从定义它的类本身、从其整个包以及从其子类(即使它们不在同一个包中)访问。

受保护属性和方法通常用于可测试性。由于测试位于与代码相同的包中,因此它们可以访问这些属性。

# 获取器

您可以使用 ES6 获取器来简化类 API

class Position {
    // More methods.
    // ...

    get offset() {
        return this.path[ this.path.length - 1 ];
    }
}

获取器应该感觉像一个自然属性。在创建获取器时,有几个建议要遵循

  • 它们应该很快。
  • 它们不应抛出异常。
  • 它们不应更改对象状态。
  • 它们不应每次都返回新对象实例(因此foo.bar == foo.bar 为真)。如果可能,为第一次调用创建一个新实例并将其缓存是可以的。

# 类定义中的顺序

在类定义中,按以下顺序排列方法和属性:

  1. 构造函数。
  2. Getter 和 Setter。
  3. 迭代器。
  4. 公共实例方法。
  5. 公共静态方法。
  6. 受保护的实例方法。
  7. 受保护的静态方法。
  8. 私有实例方法。
  9. 私有静态方法。

每个组内的顺序由实现者决定。

# 测试

测试有一些特殊的规则和技巧。

# 测试组织

  • 在测试文件中始终使用外部的 describe()。不允许在最外层的 describe() 之外使用任何全局变量,尤其是钩子(beforeEach()after() 等)。

  • 最外层的 describe() 调用应该创建有意义的组,这样当所有测试一起运行时,就可以在代码库中识别出失败的测试用例。例如

    describe( 'Editor', () => {
        describe( 'constructor()', () => {
            it( /* ... */ );
        } );
    
        // More test cases.
        // ...
    } );
    

    使用诸如 utils 之类的标题是不好的,因为整个项目中有多个实用程序。Table utils 会更好。

  • 测试描述 (it()) 应该像文档一样编写(你做了什么以及应该发生什么),例如,“当单击 X 按钮时,foo 对话框关闭”。另外,测试描述中的“...case 1”、“...case 2” 并没有帮助。

  • 避免使用诸如“当两个范围合并时不会崩溃”之类的测试描述。相反,解释预期实际发生的情况。例如:“当两个范围合并时,保留 1 个范围。”

  • 大多数情况下,使用诸如“正确地”、“工作正常”之类的词语是一种代码异味。考虑一下需求 - 在编写需求时,你不会说特性 X 应该“工作正常”。你会记录它应该如何工作。

  • 理想情况下,应该能够仅通过阅读测试描述来重新创建算法。

  • 避免在一个 it() 下涵盖多个用例。在一个测试中使用多个断言是可以的,但不要测试,例如,方法 foo() 被调用一次、两次、三次等时的工作方式。每个用例都应该有一个单独的测试。

  • 每个测试都应该在自身之后进行清理,包括销毁所有编辑器并删除所有已添加的元素。

# 测试实现

  • 避免使用实时超时。尽可能使用 模拟计时器。超时会使测试变得非常慢。

  • 但是,不要过度优化(尤其是因为性能在测试中不是优先事项)。在大多数情况下,为每个 it() 创建一个单独的编辑器是完全可以的(因此建议这样做)。

  • 我们的目标是覆盖所有不同场景的 100%。覆盖代码中 100% 的分支不是这里的目标 - 它是覆盖真实场景的副产品。

    想想这个:当你通过向现有函数调用添加参数来修复 bug 时,你不会影响代码覆盖率(该行无论如何都被调用了)。但是,你遇到了 bug,这意味着你的测试套件没有覆盖它。因此,必须为该代码更改创建测试。

  • 它应该是 expect( x ).to.equal( y )而不是expect( x ).to.be.equal( y )

  • 当使用 Sinon 间谍时,请注意断言和错误消息的可读性。

    • 使用命名间谍,例如

      const someCallbackSpy = sinon.spy().named( 'someCallback' );
      const myMethodSpy = sinon.spy( obj, 'myMethod' );
      
    • 使用 sinon-chai 断言

      expect( myMethodSpy ).to.be.calledOnce
      // expected myMethod to be called once but was called twice
      

# 命名

# JavaScript 代码名称

变量、函数、命名空间、参数以及所有未记录的用例必须以 小驼峰命名法 命名

let a;
let myLongNamedVariable = true;

function foo() {}

function longNamedFunction( example, longNamedParameter ) {}

类必须以 大驼峰命名法 命名

class MyClass() {}

const a = new MyClass();

Mixin 必须以 大驼峰命名法 命名,后缀为“Mixin”

const SomeMixin = {
    method1: /* ... */,
    method2: /* ... */
};

全局命名空间变量必须以 全大写 命名

const CKEDITOR_TRANSLATIONS = {};

# 私有属性和方法

私有属性和方法以下划线为前缀

CKEDITOR._counter;
something._doInternalTask();

# 方法和函数

方法和函数几乎总是动词或动作

// Good
execute();
this.getNextNumber()

// Bad
this.editable();
this.status();

# 属性和变量

属性和变量几乎总是名词

const editor = this.editor;
this.name;
this.editable;

布尔属性和变量始终以助动词为前缀

this.isDirty;
this.hasChildren;
this.canObserve;
this.mustRefresh;

# 按钮、命令和插件

# 按钮

所有按钮都应该遵循动词 + 名词名词约定。示例

  • 动词 + 名词约定
    • insertTable
    • selectAll
    • uploadImage
  • 名词约定
    • bold
    • mediaEmbed
    • restrictedEditing

# 命令

至于命令,则比较棘手。与按钮相比,它们的名称组合更多。示例

  • 与功能相关的约定
    • 基于名词的情况
      • codeBlock
      • fontColor
      • shiftEnter
    • 基于动词的情况
      • indent
      • removeFormat
      • selectAll
  • 功能 + 子功能约定
    • imageStyle
    • imageTextAlternative
    • tableAlignment

对于命令,名词 + 动词(或功能 + 动作)命名约定不应使用,因为听起来不自然(做什么做什么)。在大多数情况下,正确的名称应该以动作开头,然后是功能名称

  • checkTodoList
  • insertTable
  • uploadImage

# 插件

插件应该遵循功能功能 + 子功能约定。示例

  • 功能约定
    • Bold
    • Paragraph
    • SpecialCharacters
  • 功能 + 子功能约定
    • ImageResize
    • ListProperties
    • TableClipboard

插件必须以 大驼峰命名法 命名。

# 快捷键

对于局部变量,普遍接受的简短版本可以用于长名称

const attr, doc, el, fn, deps, err, id, args, uid, evt, env;

以下是唯一接受的用于属性名称的简短版本

this.lang;
this.config;
this.id;
this.env;

# 首字母缩略词和专有名词

首字母缩略词以及部分专有名词自然以大写形式书写。这可能与上面描述的代码风格规则相冲突 - 特别是在需要在变量或类名中包含首字母缩略词或专有名词时。在这种情况下,应该遵循以下规则

  • 首字母缩略词
    • 如果在变量名的开头,则全部小写:let domError
    • 如果在类名的开头,则默认使用小驼峰命名法:class DomError
    • 如果在变量或类名内,则默认使用小驼峰命名法:function getDomError()
  • 专有名词
    • 如果在变量名的开头,则全部小写:let ckeditorError
    • 如果在类名的开头,则使用原始的大小写:class CKEditorError
    • 如果在变量或类名内,则使用原始的大小写:function getCKEditorError()

但是,两个字母的首字母缩略词和专有名词(如果最初以大写形式书写)应该以大写形式书写。例如:getUI,而不是 getUi

两个最常用的会导致问题的首字母缩略词

  • DOM - 它应该是例如 getDomNode()
  • HTML - 它应该是例如 toHtml()

# CSS 类

CSS 类命名模式基于 BEM 方法和代码风格。所有名称都以小写形式书写,单词之间可以选择使用破折号 (-)。

顶级构建以强制性的 ck- 前缀开头

.ck-dialog
.ck-toolbar
.ck-dropdown-menu

属于块命名空间的元素以双下划线 (__) 分隔

.ck-dialog__header
.ck-toolbar__group
.ck-dropdown-menu__button-collapser

修饰符以单个下划线 (_) 分隔。键值修饰符
遵循 block-or-element_key_value 命名模式

/* Block modifier */
.ck-dialog_hidden
/* Element modifier */
.ck-toolbar__group_collapsed
/* Block modifier as key_value  */
.ck-dropdown-menu_theme_some-theme

在 HTML 中

<div class="ck-reset_all ck-dialog ck-dialog_theme_lark ck-dialog_visible">
    <div class="ck-dialog__top ck-dialog__top_small">
        <h1 class="ck-dialog__top-title">Title of the dialog</h1>
        ...
    </div>
    ...
</div>

# ID 属性

HTML ID 属性命名模式遵循 CSS 类 命名指南。每个 ID 必须以 ck- 前缀开头,并且由以小写形式书写的破折号分隔 (-) 的单词组成

<div id="ck">...</div>
<div id="ck-unique-div">...</div>
<div id="ck-a-very-long-unique-identifier">...</div>

# 文件名

文件和目录名称必须遵循标准,使它们的语法易于预测

  • 全部小写。
  • 只接受字母数字字符。
  • 单词用破折号 (-) 分隔 (kebab-case)。
    • 代码实体被视为单个单词,因此 DataProcessor 类在 dataprocessor.js 文件中定义。
    • 但是,一个测试文件涵盖了“多根编辑器中的突变”:mutations-in-multi-root-editors.js
  • HTML 文件具有 .html 扩展名。

# 示例

  • ckeditor.js
  • tools.js
  • editor.js
  • dataprocessor.js
  • build-all.jsbuild-min.js
  • test-core-style-system.html

# 标准文件

广泛使用的标准文件不遵守上述规则

  • README.mdLICENSE.mdCONTRIBUTING.mdCHANGES.md
  • .gitignore 和所有标准的“点文件”
  • node_modules

# CKEditor 5 自定义 ESLint 规则

除了 ESLint 提供的规则外,CKEditor 5 还使用以下描述的一些自定义规则。

# 在包之间导入:ckeditor5-rules/no-relative-imports

从同一个包导入模块时,可以使用相对路径,例如

// Assume we edit a file located in the path: `packages/ckeditor5-engine/src/model/model.js`

import Position from './position';
import insertContent from './utils/insertcontent';

从其他包导入模块时,不允许使用相对路径,并且必须使用包名进行导入,例如

👎  此规则不正确代码的示例

// Assume we edit a file located in the path: `packages/ckeditor5-engine/src/model/model.js`

import CKEditorError from '../../../ckeditor5-utils/src/ckeditorerror';

即使导入语句在本地有效,它也会在开发人员从 npm 安装包时引发错误。

👍  此规则正确代码的示例

// Assume we edit a file located in the path: `packages/ckeditor5-engine/src/model/model.js`

import { CKEditorError } from 'ckeditor5';

更改的历史记录。

# 错误的描述:ckeditor5-rules/ckeditor-error-message

每次创建新的错误时,都需要一个描述以在 错误代码 页面上显示,例如

👎  此规则不正确代码的示例

// Missing the error's description.

throw new CKEditorError( 'ckeditor5-example-error', this );

// ESLint shouldn't expect the definition of the error as it is already described.

throw new CKEditorError( 'editor-wrong-element', this );

👍  此规则正确代码的示例

// This error occurs for the first time in the project, so it needs to be defined.

/**
 * Description of why the error was thrown and how to fix the code.
 *
 * @error ckeditor5-example-error
 */
throw new CKEditorError( 'ckeditor5-example-error', this );

// This error is already described, so we don't need to provide its documentation.
// We need to disable ESLint for checking the rule.
// It is a good practice to include a note that explains where it is described.

// Documented in core/editor/editor.js
// eslint-disable-next-line ckeditor5-rules/ckeditor-error-message
throw new CKEditorError( 'editor-wrong-element', this );

更改的历史记录。

# DLL 构建:ckeditor5-rules/ckeditor-imports

为了使 CKEditor 5 插件彼此兼容,我们需要在从包中导入文件时引入限制。

标记为“Base DLL 构建”的包可以彼此之间无限制地导入。这些包的名称在 DLL 构建 指南中指定。

其他 CKEditor 5 功能(非 DLL)可以使用 ckeditor5 包导入“Base DLL”包。

ckeditor5 包导入模块时,所有导入都必须来自 src/ 目录。其他目录不会发布到 npm,因此此类导入将无法工作。

👎  此规则不正确代码的示例

// Assume we edit a file located in the path: `packages/ckeditor5-basic-styles/src/bold.js`

import { Plugin } from '@ckeditor/ckeditor5-core';

// The import uses the `ckeditor5` package, but the specified path does not exist when installing the package from npm.

import { Plugin } from 'ckeditor5/packages/ckeditor5-core';

👍  此规则正确代码的示例

// Assume we edit a file located in the path: `packages/ckeditor5-basic-styles/src/bold.js`

import { Plugin } from 'ckeditor5/src/core';

此外,非 DLL 包不应该在非 DLL 包之间导入,以避免在构建 DLL 构建时出现代码重复。

👎  此规则不正确代码的示例

// Assume we edit a file located in the path: `packages/ckeditor5-link/src/linkimage.js`

import { createImageViewElement } from '@ckeditor/ckeditor5-image'

要使用 createImageViewElement() 函数,请考虑实现一个实用程序插件,该插件将在 ckeditor5-image 包中公开所需函数。

从另一个 DLL 包导入 DLL 包时,导入语句必须使用导入包的完整名称,而不是使用 ckeditor5 符号。

👎  此规则不正确代码的示例

// Assume we edit a file located in the path: `packages/ckeditor5-widget/src/widget.js`

import { Plugin } from 'ckeditor5/src/core';

👍  此规则正确代码的示例

// Assume we edit a file located in the path: `packages/ckeditor5-widget/src/widget.js`

import { Plugin } from 'ckeditor5';

更改历史记录

#跨包导入:ckeditor5-rules/no-cross-package-imports

允许从其他包导入模块

import { toArray } from 'ckeditor5/src/utils';

但是,某些包无法从 CKEditor 5 导入模块,因为这会导致代码重复和运行时错误。因此,该规则禁用此类导入。

目前,它适用于 @ckeditor/ckeditor5-watchdog 包。

👎  此规则不正确代码的示例

// Assume we edit a file located in the `packages/ckeditor5-watchdog/` directory.

import { toArray } from 'ckeditor5/src/utils';
import { toArray } from 'ckeditor5';

更改的历史记录。

#在调试注释中导入模块:ckeditor5-rules/use-require-for-debug-mode-imports

调试模式允许导入其他模块以进行测试。不幸的是,调试注释不会被删除,因此 webpack 会报告以下错误。

Module parse failed: 'import' and 'export' may only appear at the top level (15204:20)
File was processed with these loaders:
 * ./node_modules/@ckeditor/ckeditor5-dev-tests/lib/utils/ck-debug-loader.js
You may need an additional loader to handle the result of these loaders.
|  */
|
> /* @if CK_DEBUG */  import { CKEditorError } from 'ckeditor5/src/utils';
|
| /**

模块需要使用 require() 函数导入。

要创建仅在调试模式下执行的代码,请遵循 测试环境 指南中 --debug 标志的说明。

👎  此规则不正确代码的示例

// @if CK_DEBUG // import defaultExport from 'module-name';
// @if CK_DEBUG // import * as name from 'module-name';
// @if CK_DEBUG // import { testFunction } from 'module-name';
// @if CK_DEBUG // import { default as alias } from 'module-name';
// @if CK_DEBUG // import { exported as alias } from 'module-name';
// @if CK_DEBUG // import 'module-name';

👍  此规则正确代码的示例

// @if CK_DEBUG // const defaultExport = require( 'module-name' ).default;
// @if CK_DEBUG // const name = require( 'module-name' );
// @if CK_DEBUG // const { testFunction } = require( 'module-name' );
// @if CK_DEBUG // const alias = require( 'module-name' ).default;
// @if CK_DEBUG // const { exported: alias } = require( 'module-name' );
// @if CK_DEBUG // require( 'module-name' );

更改的历史记录。

#标记为 @internal 的非公共成员:ckeditor5-rules/non-public-members-as-internal

此规则应仅用于 .ts 文件。

要从类型定义中删除非公共成员,请在成员的 JSDoc 中使用 @internal 标签。

然后,您可以在 tsconfig.json 中使用 "stripInternal": true 选项将其从声明类型中删除。

👎  此规则不正确代码的示例

class ClassWithSecrets {
    private _shouldNotBeEmitted: string;
}

👍  此规则正确代码的示例

class ClassWithSecrets {
    /**
    * @internal
    */
    private _shouldNotBeEmitted: string;
}

#导入预定义的构建:ckeditor5-rules/no-build-extensions

此规则仅适用于文档中的代码片段。

在导入预定义的构建时,只允许导入此构建,如下所示

// Assume we edit a file located in the path: `packages/ckeditor5-alignment/docs/_snippets/features/text-alignment.js`.

import ClassicEditor from '@ckeditor/ckeditor5-build-classic';

不允许从 src 目录导入任何内容以扩展 CKEditor 5 构建。预定义构建中的其他目录不会发布到 npm,因此此类导入将不起作用。

👎  此规则不正确代码的示例

// Assume we edit a file located in the path: `packages/ckeditor5-alignment/docs/_snippets/features/text-alignment.js`.

import ClassicEditor from '@ckeditor/ckeditor5-build-classic/src/ckeditor';
// Assume we edit a file located in the path: `docs/_snippets/features/placeholder.js`.

import ClassicEditor from '@ckeditor/ckeditor5-build-classic/src/ckeditor';

更改的历史记录。

#为核心包声明模块增强:ckeditor5-rules/allow-declare-module-only-in-augmentation-file

此规则应仅用于 .ts 文件。

大多数模块中的主要入口点(index.ts 文件)都有一个副作用导入 import './augmentation',该导入使用模块增强来使用有关可用插件、配置和命令的信息填充编辑器类型。

此规则强制所有 declare module '@ckeditor/ckeditor5-core' 在此 augmentation.ts 文件中定义。

#从模块导入:ckeditor5-rules/allow-imports-only-from-main-package-entry-point

此规则确保所有来自 @ckeditor/* 包的导入都是通过主要包入口点完成的。这是编辑器类型正常工作并简化迁移到 CKEditor 5 v42.0.0 中引入的安装方法所必需的。

👎 此规则的错误代码示例

// Importing from the `/src/` folder is not allowed.
import Table from '@ckeditor/ckeditor5-table/src/table';

// Importing from the `/theme/` folder is not allowed.
import BoldIcon from '@ckeditor/ckeditor5-core/theme/icons/bold.svg';

👍  此规则正确代码的示例

// ✔️ Importing from the main entry point is allowed.
import { Table } from 'ckeditor5';

#需要 as constckeditor5-rules/require-as-const-returns-in-methods

此规则应仅用于 .ts 文件。

在 TypeScript 中,从某些值推断出的类型被简化了。例如,const test = [1, 2, 3]; 的类型是 number[],但在某些情况下可能需要更具体的类型。使用 as const 可以帮助解决这个问题。例如,const test1 = [1, 2, 3] as const; 的类型是 readonly [1, 2, 3]

require-as-const-returns-in-methods 规则要求某些依赖于返回数据的精确类型的方法(例如,pluginName 方法中的泛型 string 而不是 'delete' 文字字符串,或者 requires 方法中的 [] 而不是 readonly [typeof Table])具有所有带有 as const 的 return 语句。

👎  此规则不正确代码的示例

export default class Delete extends Plugin {
    public static get pluginName(): string {
        return 'Delete';
    }
}
export default class Delete extends Plugin {
    public static get pluginName(): 'Delete' {
        return 'Delete';
    }
}

👍  此规则正确代码的示例

export default class Delete extends Plugin {
    public static get pluginName() {
        return 'Delete' as const;
    }
}

#包内的导入:ckeditor5-rules/no-scoped-imports-within-package

在每个包中定义的所有导入,指向同一个包中的文件,都必须是相对的。如果目标文件与导入声明位于同一个包中,则不能使用范围导入。解析后的范围导入指向 node_modules 中的包,而不是当前工作目录,这两个位置的源代码可能彼此不同。

👎  此规则不正确代码的示例

// Assume we edit a file located in the path: `packages/ckeditor5-alignment/src/alignment.ts`.

// Both imports are incorrect.
import { AlignmentEditing } from '@ckeditor/ckeditor5-alignment';
import AlignmentEditing from '@ckeditor/ckeditor5-alignment/src/alignmentediting';

👍  此规则正确代码的示例

// Assume we edit a file located in the path: `packages/ckeditor5-alignment/src/alignment.ts`.

import AlignmentEditing from './alignmentediting';

更改的历史记录。

#导入中强制使用文件扩展名:ckeditor5-rules/require-file-extensions-in-imports

根据 ECMAScript (ESM) 标准的要求,所有导入和重新导出都必须包含文件扩展名。如果它们不包含它,此规则将尝试自动检测正确的文件扩展名。在以下两种情况下这是不可能的

  • 文件扩展名与 .ts.js.json 不同。
  • 文件在文件系统中不存在。

第二种情况在文档文件中很常见,因为它的部分位于不同的目录和存储库中。这些部分在构建步骤期间合并,但在合并之前,导入在技术上是无效的。

在这种情况下,您必须手动添加文件扩展名。带有文件扩展名的导入不会被验证。

#要求或禁止某些插件标志:ckeditor5-rules/ckeditor-plugin-flags

此规则应仅用于 .ts 文件。

此规则确保插件标志正确设置或根本没有设置。它检查标志是否具有正确的类型和值,防止常见错误并确保符合 CKEditor 5 插件 API。

选项

  • requiredFlags – (可选)必须在插件中设置的标志数组。
  • disallowedFlags – (可选)必须在插件中未设置的标志数组。

下面的示例配置要求将 isFooPlugin 标志设置为 true,并且禁止 isBarPlugin 标志

{
    "requiredFlags": [
        {
            "name": "isFooPlugin",
            "returnValue": true
        }
    ],
    "disallowedFlags": [ "isBarPlugin" ]
}

👎  此规则不正确代码的示例

export default class MyPlugin extends Plugin {
    static get pluginName() {
        return 'MyPlugin';
    }

    public static override get isBarPlugin(): false {
        return false;
    }
}

isBarPlugin 标志被禁止,插件将其设置为 false。此外,isFooPlugin 标志是必需的,但未定义。

👍  此规则正确代码的示例

export default class MyPlugin extends Plugin {
    static get pluginName() {
        return 'MyPlugin';
    }

    public static override get isFooPlugin(): true {
        return true;
    }
}

isFooPlugin 标志是必需的,并设置为 trueisBarPlugin 标志未定义。

#无遗留导入

此规则确保使用 新的安装方法 进行导入。所有导入都应使用 ckeditor5 包来获取编辑器核心和所有开源插件,或者使用 ckeditor5-premium-features 来获取高级功能。

👎  此规则不正确代码的示例

// Import from `ckeditor5/src/*`.
import { Plugin } from 'ckeditor5/src/core.js';

// Import from individual open-source package.
import { Plugin } from '@ckeditor/ckeditor5-core';

// Import from individual premium package.
import { AIAssistant } from '@ckeditor/ckeditor5-ai';

👍  此规则正确代码的示例

import { Plugin } from 'ckeditor5';
import { AIAssistant } from 'ckeditor5-premium-features';