# @codemod/matchers 中文文档

规范地址：https://npmcn.com/package/%40codemod/matchers/

版本：1\.7\.1
npm 元数据更新时间：2024-03-10T23:23:55.869Z
中文译文更新时间：2026-11-10T07:16:14.285Z
软件包许可：Apache-2\.0

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40codemod/matchers
原文对照：https://npmcn.com/package/%40codemod/matchers/?lang=original
项目主页：https://github.com/codemod-js/codemod#readme
源码仓库：https://github.com/codemod-js/codemod

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：769 kB，75 个文件
- 模块格式：CommonJS
- TypeScript 类型：包内自带
- 运行依赖：2 个
- 首次发布：2019-02-08T19:38:52.295Z
- 最新版本发布：2024-03-10T23:23:55.202Z
- 近 30 天下载量：79213（2026-10-10 至 2026-11-08，npm 官方下载统计）
- 已发布版本数：24（近 12 个月 0 个）

## 中文简介

JavaScript & TypeScript 编码器的匹配器.

## 安装

```sh
npm install @codemod/matchers
```

也可以使用：`pnpm add @codemod/matchers`、`yarn add @codemod/matchers`、`bun add @codemod/matchers`

## README

# @ codemod/ 匹配器

JavaScript & TypeScript 编码器的匹配器.

## 安装

从 [无](https://npmjs.com/)数字 :

```sh
$ npm install @codemod/matchers
```

## 使用量

> 本软件包主要用于编码器 `@codemod/cli`,但可以在任何 Babel 插件或 JavaScript/ TypeScript AST 处理器中使用. 请注意,以下例子均载于 [类型脚本](https://typescriptlang.org/),但在大多数情况下,与它们的JavaScript对应的相同.

使用此软件包的最简单方法是使用 `@codemod/cli` 像这样:

```ts
// `t` is `@babel/types`
import { defineCodemod, t } from '@codemod/cli'

// `m` is the `@codemod/matchers` module
export default defineCodemod(({ m }) => ({
  visitors: {
    // …
  },
}))
```

其余的 README 显示如何使用 `@codemod/matchers` 无 `@codemod/cli`,但同样的模式适用.

### 简单匹配

正如您可以构建 AST 节点 `@babel/types`您可以构建 AST 节点 匹配一个精确的节点 `@codemod/matchers`数字 :

```ts
import * as m from '@codemod/matchers'
import * as t from '@babel/types'

// `matcher` only matches Identifier nodes named 'test'
const matcher = m.identifier('test')

matcher.match(t.identifier('test')) // true
matcher.match(t.identifier('test2')) // false
```

### 模糊匹配

然而,匹配精确的节点通常不是你想要的. `@codemod/matchers` 可以在仅指定部分数据的地方构建匹配器:

```ts
import * as m from '@codemod/matchers'
import * as t from '@babel/types'

// `matcher` matches any Identifier, regardless of name
const matcher = m.identifier()

matcher.match(t.identifier('test')) // true
matcher.match(t.identifier('test2')) // true
matcher.match(t.emptyStatement()) // false
```

这里有一个比较复杂的例子 匹配任何 `console.log` 打电话 假设 `expr` 将联署材料描述为:

```ts
import * as m from '@codemod/matchers'

// `matcher` matches any `console.log(…)` call
const matcher = m.callExpression(
  m.memberExpression(m.identifier('console'), m.identifier('log'), false)
  // `arguments` is omitted to match anything, or we could pass `m.anything()`
)

matcher.match(expr('console.log()')) // true
matcher.match(expr('console.log(1, 2)')) // true
matcher.match(expr('console.log')) // false
```

有各种各样的模糊的相配者 `@codemod/matchers`数字 :

```ts
import * as m from '@codemod/matchers'

m.anyString().match('a string') // true
m.anyString().match(1) // false

m.anyNumber().match(1) // true
m.anyNumber().match('a string') // false

m.anything().match(1) // true
m.anything().match('a string') // true
m.anything().match(expr('foo')) // true
m.anything().match(null) // true

m.anyNode().match(expr('a + b')) // true
m.anyNode().match(expr('!a')) // true
m.anyNode().match(1) // false
m.anyNode().match('a string') // false
```

### 配置匹配

通常你会想捕捉你所匹配的部分节点,以便从中提取信息或编辑信息.

```ts
import * as m from '@codemod/matchers'

// matches `console.<consoleMethod>(…)` calls
const consoleMethod = m.capture(m.identifier())
const matcher = m.callExpression(
  m.memberExpression(m.identifier('console'), consoleMethod, false)
  // `arguments` is omitted to match anything, or we could pass `m.anything()`
)

if (matcher.match(expr('console.log()'))) {
  console.log(`found console call: ${consoleMethod.current.name}`)
}

if (matcher.match(expr('console.group("hi!")'))) {
  console.log(`found console call: ${consoleMethod.current.name}`)
}

if (matcher.match(expr('notAConsoleCall()'))) {
  console.log(`found console call: ${consoleMethod.current.name}`)
}

// logs:
//   found console call: log
//   found console call: group
```

### 反引用

有时你会想在选手的后期部分提及一个被捕获的早期值. 例如,让我们假设您想要匹配一个函数表达式,该函数表达式返回它的参数:

```ts
import * as m from '@codemod/matchers'

const argumentMatcher = m.capture(m.identifier())
const matcher = m.functionExpression(
  m.anything(),
  [argumentMatcher],
  m.blockStatement([m.returnStatement(m.fromCapture(argumentMatcher))])
)

matcher.match(expr('function(a) { return a; })')) // true
matcher.match(expr('function id(a) { return a; })')) // true
matcher.match(expr('function(a) { return b; })')) // false
matcher.match(expr('function(a) { return 1; })')) // false
matcher.match(expr('function(a) { return a + a; })')) // false
```

### 在编码器中使用

所有之前的例子都有匹配者测试特定的AST节点. 这对插图很有用,但通常你不会用它。 编码器为 `@codemod/cli` 为: [Babel 插件](https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md) 因此,使用访问者模式处理AST。 下面是上面的例子,它可以识别那些只做不做而再次返回其参数的函数,这次是一个 Babel 插件,它用全局替换了这些函数 `IDENTITY` 参考文献 :

```ts
/**
 * Replaces identity functions with `IDENTITY`:
 *
 *   list.filter(function(a) { return a; });
 *
 * becomes:
 *
 *   list.filter(IDENTITY);
 */
import * as m from '@codemod/matchers'
import * as t from '@babel/types'
import { NodePath } from '@babel/traverse'

export default function () {
  return {
    visitor: {
      FunctionExpression(path: NodePath<t.FunctionExpression>): void {
        const argumentMatcher = m.capture(m.identifier())
        const matcher = m.functionExpression(
          m.anything(),
          [argumentMatcher],
          m.blockStatement([m.returnStatement(m.fromCapture(argumentMatcher))])
        )

        if (matcher.match(path.node)) {
          path.replaceWith(t.identifier('IDENTITY'))
        }
      },
    },
  }
}
```

这里又是一个没有使用过的插件 `@codemod/matchers`数字 :

```ts
/**
 * Replaces identity functions with `IDENTITY`:
 *
 *   list.filter(function(a) { return a; });
 *
 * becomes:
 *
 *   list.filter(IDENTITY);
 */
import * as t from '@babel/types'
import { NodePath } from '@babel/traverse'

export default function () {
  return {
    visitor: {
      /**
       * This version of the codemod, which does not use `@codemod/matchers`,
       * is more verbose and more likely to have subtle bugs. For example,
       * it's easy to forget to check that the `return` statement actually has a
       * return value before checking that it is an identifier, which would
       * result in a crash.
       */
      FunctionExpression(path: NodePath<t.FunctionExpression>): void {
        // ensure function has exactly one parameter
        if (path.node.params.length !== 1) {
          return
        }

        // ensure parameter is an identifier
        const param = path.node.params[0]
        if (!t.isIdentifier(param)) {
          return
        }

        // ensure function body has exactly one statement
        if (path.node.body.body.length !== 1) {
          return
        }

        // ensure that statement is a return statement
        const statement = path.node.body.body[0]
        if (!t.isReturnStatement(statement)) {
          return
        }

        // ensure the return actually returns something, an identifier
        if (!statement.argument || !t.isIdentifier(statement.argument)) {
          return
        }

        // ensure returned identifier has same name as the param
        if (statement.argument.name !== param.name) {
          return
        }

        // replace!
        path.replaceWith(t.identifier('IDENTITY'))
      },
    },
  }
}
```

### 深度匹配

有时候你知道你想匹配一个节点,但不知道它在树上深度,因此无法将整个匹配树进行硬码. 为了应付这种情况,你可以使用 `containerOf` 选手。 例如,这个匹配者会找到第一个 `done` 在摩卡测试中调用, 计算参数可能使用的名称 :

```ts
import * as m from '@codemod/matchers'

const doneParam = m.capture(m.identifier())
const matcher = m.callExpression(m.identifier('test'), [
  m.anyString(),
  m.function(
    [doneParam],
    m.containerOf(m.callExpression(m.fromCapture(doneParam)))
  ),
])

// matches because there's a `done()` call
matcher.match(
  expr(`
  test('setTimeout calls back around N ms later', function(done) {
    const now = Date.now();
    const duration = 5;

    setTimeout(function() {
      assert.ok(Date.now() - now < 10);
      done();
    }, duration);
  });
`)
)

// does not match because there's no `done()` call
matcher.match(
  expr(`
  test('adds things', function() {
    assert.strictEqual(3 + 4, 7);
  });
`)
)
```

### 自定义匹配符

构建自定义匹配器最简单的方法就是编组出现有的匹配器:

```ts
import * as m from '@codemod/matchers'
import * as t from '@babel/types'

function plusEqualOne() {
  return m.assignmentExpression(
    '+=',
    m.anything(), // or just `undefined` for the same effect
    m.numericLiteral(1)
  )
}

const matcher = plusEqualOne()

matcher.match(expr('a += 1')) // true
matcher.match(expr('a.b += 1')) // true
matcher.match(expr('a -= 1')) // false
matcher.match(expr('a += 2')) // false
```

您可以轻松使用上游构建简单的自定义匹配器 :

```ts
import * as m from '@codemod/matchers'

const oddNumberMatcher = m.matcher(
  (value) => typeof value === 'number' && Math.abs(number % 2) === 1
)

oddNumberMatcher.match(expr('-1')) // true
oddNumberMatcher.match(expr('0')) // false
oddNumberMatcher.match(expr('1')) // true
oddNumberMatcher.match(expr('2')) // true
oddNumberMatcher.match(expr('3')) // true
oddNumberMatcher.match(expr('Infinity')) // false
oddNumberMatcher.match(expr('NaN')) // false
```

这种匹配器很容易通过将它包入函数来参数化:

```ts
import * as m from '@codemod/matchers';

function stringMatching(pattern: RegExp) {
  return m.matcher(
    value => typeof value === 'string' && pattern.test(value)
  );
)

const startsWithRun = stringMatching(/^run/);

startsWithRun.match('run');     // true
startsWithRun.match('runner');  // true
startsWithRun.match('running'); // true
startsWithRun.match('ruining'); // false
startsWithRun.match(' run');    // false
startsWithRun.match('');        // false
startsWithRun.match(1);         // false
```

一个常见的情况是,你认为你需要一个自定义匹配者,就是当你想要一些可能的值之一的时候. 在这种情况下,您可以使用 `or` 匹配者 :

```ts
import * as m from '@codemod/matchers'

const matcher = m.or(m.anyString(), m.anyNumber())

matcher.match(1) // true
matcher.match('string') // true
matcher.match({}) // false
matcher.match(expr('1')) // false
```

在处理函数等事项时,匹配少数数值之一是常见的,这些函数可以是箭头函数,函数表达式,或函数声明. 这里有一个更普通的版本 `IDENTITY` 使用 `or` 匹配器,用于替换箭头函数 :

```ts
/**
 * Replaces identity functions with `IDENTITY`:
 *
 *   list.filter(function(a) { return a; });
 *   list2.filter(a => a);
 *
 * becomes:
 *
 *   list.filter(IDENTITY);
 *   list2.filter(IDENTITY);
 */
import * as m from '@codemod/matchers'
import * as t from '@babel/types'
import { NodePath } from '@babel/traverse'

export default function () {
  return {
    visitor: {
      FunctionExpression(path: NodePath<t.FunctionExpression>): void {
        const paramId = m.capture(m.identifier())
        const matcher = m.function(
          [paramId],
          m.or(
            m.blockStatement([m.returnStatement(m.fromCapture(paramId))]),
            m.fromCapture(paramId)
          )
        )

        if (matcher.match(path.node)) {
          path.replaceWith(t.identifier('IDENTITY'))
        }
      },
    },
  }
}
```

你可能不需要它, 但你可以自己建造 通过子分类 `Matcher`这里也是 `stringMatching` 但作为一个子类 `Matcher`数字 :

```ts
import * as m from '@codemod/matchers'
import * as t from '@babel/types'

// This is more ceremony than the simple predicate-based one above.
class StringMatching extends m.Matcher<string> {
  constructor(private readonly pattern: RegExp) {
    super()
  }

  matchValue(
    value: unknown,
    keys: ReadonlyArray<PropertyKey>
  ): value is string {
    return typeof value === 'string' && this.pattern.test(value)
  }
}

const startsWithRun = new StringMatching(/^run/)

startsWithRun.match('run') // true
startsWithRun.match('runner') // true
startsWithRun.match('running') // true
startsWithRun.match('ruining') // false
startsWithRun.match(' run') // false
startsWithRun.match('') // false
startsWithRun.match(1) // false
```

## 捐款

见 [捐款额](https://npmcn.com/package/CONTRIBUTING.md) (c) 提供关于设立发展项目和为项目作贡献的信息.

## 许可证

版权 2019 Brian Donovan

根据 Apache 许可证, 版本 2. 0 (“ License ”) 获得许可; 您不能使用此文件, 除非符合许可证 。 您可在下列地点获得许可证副本:

```
http://www.apache.org/licenses/LICENSE-2.0
```

除非适用的法律要求或书面同意,根据许可证分发的软件在 " AS IS " BASIS上分发,没有任何明示或默示的WARANTIES或CONDITION。 见《许可证》关于许可和许可证限制的具体语言.

## 常见问题

### @codemod/matchers 是什么？

JavaScript & TypeScript 编码器的匹配器\.

### 如何安装 @codemod/matchers？

在项目目录运行 npm install @codemod/matchers，或使用 pnpm add / yarn add / bun add。

### @codemod/matchers 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @codemod/matchers 支持 ESM 还是 CommonJS？

以 CommonJS 形式发布，可以直接 require\(\)；在 ES Module 项目中也可以用 import 引入（Node\.js 会把 module\.exports 作为默认导出）。

### @codemod/matchers 可以商用吗？

@codemod/matchers 使用 Apache-2\.0 许可。宽松许可：允许商业使用、修改和再分发，并附带明确的专利授权；分发时需保留许可与 NOTICE 文件，修改过的文件要注明改动。以上为概括，不构成法律意见，请以许可原文为准。

### @codemod/matchers 的下载量有多少？

根据 npm 官方下载统计，@codemod/matchers 在 2026/10/10 – 2026/11/08 这 30 天内被下载 79,213 次，日均约 2,640 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @codemod/matchers 还在维护吗？

最新版本 1\.7\.1 发布于 2024/03/11（2 年前）；至今共发布 24 个版本，其中过去 12 个月发布了 0 个；首次发布于 2019/02/09。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

