Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: switch from tap to node:test and Borp #39

Merged
merged 3 commits into from
Mar 12, 2024
Merged

Conversation

EvanHahn
Copy link
Contributor

@EvanHahn EvanHahn commented Feb 28, 2024

To do this, I:

  • Uninstalled tap and replaced it with borp
  • Used c8 for test coverage
    • This changed the way coverage was detected around the ??= operator, so I had to write an additional unit test
  • Wrote a jscodeshift codemod (below)
  • Dropped Node 16 support, which node:test requires
  • Made a few small manual tweaks
Here's the jscodeshift transformer:
import type {
  Transform,
  Expression,
  FunctionExpression,
  ArrowFunctionExpression,
} from 'jscodeshift'

const isFunctionExpression = (
  expression: Readonly<Expression>
): expression is FunctionExpression | ArrowFunctionExpression =>
  expression.type === 'FunctionExpression' ||
  expression.type === 'ArrowFunctionExpression'

const transform: Transform = (fileInfo, api) => {
  const { j } = api
  const root = j(fileInfo.source)

  // Update imports

  {
    const commonJsImport = (varName: string, moduleName: string) =>
      j.variableDeclaration('const', [
        j.variableDeclarator(
          j.identifier(varName),
          j.callExpression(j.identifier('require'), [
            j.stringLiteral(moduleName),
          ])
        ),
      ])

    const tapImport = root.find(j.VariableDeclaration, {
      declarations: [
        {
          init: {
            callee: { name: 'require' },
            arguments: [
              {
                value: 'tap',
              },
            ],
          },
        },
      ],
    })

    tapImport.insertBefore(commonJsImport('test', 'node:test'))
    tapImport.insertBefore(commonJsImport('assert', 'node:assert/strict'))

    tapImport.remove()
  }

  // Drop args from test callback

  root
    .find(j.CallExpression, { callee: { name: 'test' } })
    .forEach((callExpression) => {
      for (const arg of callExpression.value.arguments) {
        if (!isFunctionExpression(arg)) continue
        arg.params = []
      }
    })

  // Drop t.pass

  root
    .find(j.ExpressionStatement, {
      expression: {
        callee: {
          type: 'MemberExpression',
          object: { name: 't' },
          property: { name: 'pass' },
        },
      },
    })
    .remove()

  // Simple replacements of various calls

  {
    const toReplace: Map<string, string> = new Map([
      ['comment', 'console.log'],
      ['equal', 'assert.equal'],
      ['fail', 'assert.fail'],
      ['not', 'assert.notEqual'],
      ['ok', 'assert.ok'],
      ['same', 'assert.deepEqual'],
    ])
    for (const [tMethod, memberExpressionString] of toReplace) {
      const [newObj, newProp] = memberExpressionString.split('.')
      root
        .find(j.MemberExpression, {
          object: { name: 't' },
          property: { name: tMethod },
        })
        .forEach((memberExpression) => {
          memberExpression.value.object = j.identifier(newObj)
          memberExpression.value.property = j.identifier(newProp)
        })
    }
  }

  // Replace `t.notOk(x)` with `assert.equal(x, false)`.
  // Doesn't always work, but works how we use it.

  root
    .find(j.CallExpression, {
      callee: {
        type: 'MemberExpression',
        object: { name: 't' },
        property: { name: 'notOk' },
      },
    })
    .forEach((callExpression) => {
      callExpression.value.callee = j.memberExpression(
        j.identifier('assert'),
        j.identifier('equal')
      )
      callExpression.value.arguments = [
        callExpression.value.arguments[0],
        j.booleanLiteral(false),
        ...callExpression.value.arguments.slice(1),
      ]
    })

  // All done!

  return root.toSource()
}

export default transform

To do this, I:

- Uninstalled `tap` and replaced it with `borp`
- Wrote a [jscodeshift] codemod (below)
- Updated `instanbul` references to `c8`, which Borp uses internally
- Dropped Node 16 support, which `node:test` requires
- Made a few small manual tweaks

Here's the jscodeshift transformer:

```typescript
import type {
  Transform,
  Expression,
  FunctionExpression,
  ArrowFunctionExpression,
} from 'jscodeshift'

const isFunctionExpression = (
  expression: Readonly<Expression>
): expression is FunctionExpression | ArrowFunctionExpression =>
  expression.type === 'FunctionExpression' ||
  expression.type === 'ArrowFunctionExpression'

const transform: Transform = (fileInfo, api) => {
  const { j } = api
  const root = j(fileInfo.source)

  // Update imports

  {
    const commonJsImport = (varName: string, moduleName: string) =>
      j.variableDeclaration('const', [
        j.variableDeclarator(
          j.identifier(varName),
          j.callExpression(j.identifier('require'), [
            j.stringLiteral(moduleName),
          ])
        ),
      ])

    const tapImport = root.find(j.VariableDeclaration, {
      declarations: [
        {
          init: {
            callee: { name: 'require' },
            arguments: [
              {
                value: 'tap',
              },
            ],
          },
        },
      ],
    })

    tapImport.insertBefore(commonJsImport('test', 'node:test'))
    tapImport.insertBefore(commonJsImport('assert', 'node:assert/strict'))

    tapImport.remove()
  }

  // Drop args from test callback

  root
    .find(j.CallExpression, { callee: { name: 'test' } })
    .forEach((callExpression) => {
      for (const arg of callExpression.value.arguments) {
        if (!isFunctionExpression(arg)) continue
        arg.params = []
      }
    })

  // Drop t.pass

  root
    .find(j.ExpressionStatement, {
      expression: {
        callee: {
          type: 'MemberExpression',
          object: { name: 't' },
          property: { name: 'pass' },
        },
      },
    })
    .remove()

  // Simple replacements of various calls

  {
    const toReplace: Map<string, string> = new Map([
      ['comment', 'console.log'],
      ['equal', 'assert.equal'],
      ['fail', 'assert.fail'],
      ['not', 'assert.notEqual'],
      ['ok', 'assert.ok'],
      ['same', 'assert.deepEqual'],
    ])
    for (const [tMethod, memberExpressionString] of toReplace) {
      const [newObj, newProp] = memberExpressionString.split('.')
      root
        .find(j.MemberExpression, {
          object: { name: 't' },
          property: { name: tMethod },
        })
        .forEach((memberExpression) => {
          memberExpression.value.object = j.identifier(newObj)
          memberExpression.value.property = j.identifier(newProp)
        })
    }
  }

  // Replace `t.notOk(x)` with `assert.equal(x, false)`.
  // Doesn't always work, but works how we use it.

  root
    .find(j.CallExpression, {
      callee: {
        type: 'MemberExpression',
        object: { name: 't' },
        property: { name: 'notOk' },
      },
    })
    .forEach((callExpression) => {
      callExpression.value.callee = j.memberExpression(
        j.identifier('assert'),
        j.identifier('equal')
      )
      callExpression.value.arguments = [
        callExpression.value.arguments[0],
        j.booleanLiteral(false),
        ...callExpression.value.arguments.slice(1),
      ]
    })

  // All done!

  return root.toSource()
}

export default transform
```

[jscodeshift]: https://github.com/facebook/jscodeshift
@EvanHahn EvanHahn marked this pull request as ready for review March 2, 2024 18:49
@EvanHahn EvanHahn requested review from achou11 and gmaclennan March 11, 2024 21:46
Copy link
Member

@gmaclennan gmaclennan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, looks good to me

@EvanHahn EvanHahn merged commit 9b81ec1 into main Mar 12, 2024
3 checks passed
@EvanHahn EvanHahn deleted the tap-2-nodetest branch March 12, 2024 15:34
EvanHahn added a commit to digidem/mapeo-sqlite-indexer that referenced this pull request Mar 20, 2024
This was a pretty mechanical change. To do this, I:

* Uninstalled `tap` and replaced it with `borp`
* Manually rewrote tests to use `node:assert` and `t.after` for cleanup

See [multi-core-indexer#39] for a similar patch on another repo.

[multi-core-indexer#39]: digidem/multi-core-indexer#39
EvanHahn added a commit to digidem/comapeo-schema that referenced this pull request Mar 28, 2024
This was a pretty mechanical change. To do this, I:

* Uninstalled `tap` and replaced it with `node --test`
* Manually rewrote assertions to use `node:assert`

See [multi-core-indexer#39] and [mapeo-sqlite-indexer#27] for similar
patches on other repos.

[multi-core-indexer#39]: digidem/multi-core-indexer#39
[mapeo-sqlite-indexer#27]: digidem/mapeo-sqlite-indexer#27
EvanHahn added a commit to digidem/comapeo-schema that referenced this pull request Mar 28, 2024
This was a pretty mechanical change. To do this, I:

* Uninstalled `tap` and replaced it with `node --test`
* Manually rewrote assertions to use `node:assert`

See [multi-core-indexer#39] and [mapeo-sqlite-indexer#27] for similar
patches on other repos.

[multi-core-indexer#39]: digidem/multi-core-indexer#39
[mapeo-sqlite-indexer#27]: digidem/mapeo-sqlite-indexer#27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants