Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "Run typescript in custom codegen script (#228)",
"type": "minor",
"packageName": "eslint-plugin-codegen"
}
],
"packageName": "eslint-plugin-codegen",
"email": "mmkal@users.noreply.github.com"
}
44 changes: 39 additions & 5 deletions common/config/rush/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion packages/eslint-plugin-codegen/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
module.exports = require('@mmkal/rig/.eslintrc')
const base = require('@mmkal/rig/.eslintrc')
module.exports = {
...base,
ignorePatterns: [
...base.ignorePatterns,
'**/__tests__/custom-preset.js',
],
}
3 changes: 2 additions & 1 deletion packages/eslint-plugin-codegen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"expect-type": "workspace:*",
"minimatch": "3.0.4",
"read-pkg-up": "7.0.1",
"eslint": "7.15.0"
"eslint": "7.15.0",
"ts-node": "9.1.1"
}
}
12 changes: 7 additions & 5 deletions packages/eslint-plugin-codegen/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ Generates a yaml config for the [GitHub Pull Request Labeler Action](https://git


<!-- codegen:start {preset: markdownFromJsdoc, source: src/presets/custom.ts, export: custom} -->
#### [custom](./src/presets/custom.ts#L26)
#### [custom](./src/presets/custom.ts#L28)

Define your own codegen function, which will receive all options specified. Import the `Preset` type from this library to define a strongly-typed preset function:

Expand All @@ -279,10 +279,12 @@ This can be used with:

##### Params

|name |description |
|------|----------------------------------------------------------------------------------|
|source|Relative path to the module containing the custom preset |
|export|The name of the export. If omitted, the module itself should be a preset function.|
|name |description |
|-------|----------------------------------------------------------------------------------------------------------------------------------|
|source |Relative path to the module containing the custom preset. |
|export |The name of the export. If omitted, the module itself should be a preset function. |
|require|A module to load before `source`. If not set, defaults to `ts-node/register/transpile-only` for typescript sources. |
|dev |Set to `true` to clear the require cache for `source` before loading. Allows editing the function without requiring an IDE reload.|
<!-- codegen:end -->

##### Demo
Expand Down
3 changes: 3 additions & 0 deletions packages/eslint-plugin-codegen/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import * as eslint from 'eslint'
import * as presetsModule from './presets'
import expect from 'expect'

// todo: run prettier on output, if found
// todo: codegen/fs rule. type fs.anything and it generates an import for fs. same for path and os.

type MatchAll = (text: string, pattern: string | RegExp) => Iterable<NonNullable<ReturnType<string['match']>>>
// eslint-disable-next-line @typescript-eslint/no-var-requires
const matchAll: MatchAll = require('string.prototype.matchall')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const getText = () => 'typescript text'
Original file line number Diff line number Diff line change
@@ -1,45 +1,103 @@
import * as preset from '../custom'
import * as path from 'path'

test('loads custom export', () => {
jest.mock('ts-node/register/transpile-only')

test('custom preset validation', () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const customPreset = require('./custom-preset')

expect(Object.keys(customPreset)).toEqual(['getText', 'thrower'])

expect(customPreset.getText.toString().trim()).toMatch(/'Named export with input: ' \+ options.input/)
})

test('named export', () => {
expect(
preset.custom({
meta: {filename: __filename, existingContent: ''},
options: {source: './custom-preset.js', export: 'getText', input: 'abc'},
})
).toMatchInlineSnapshot(`"Named export with input: abc"`)
})

test('whole module export', () => {
expect(
preset.custom({
meta: {filename: __filename, existingContent: ''},
options: {source: './custom-preset.js', input: 'def'},
})
).toMatchInlineSnapshot(`"Whole module export with input: def"`)
})

test('load typescript with ts-node', () => {
expect(
preset.custom({
meta: {filename: __filename, existingContent: ''},
options: {source: './custom-preset.ts', export: 'getText'},
})
).toMatchInlineSnapshot(`"typescript text"`)
})

test('dev mode, deletes require cache', () => {
expect(
preset.custom({
meta: {filename: __filename, existingContent: ''},
options: {source: './custom-preset.js', input: 'ghi', dev: true},
})
).toMatchInlineSnapshot(`"Whole module export with input: ghi"`)
})

test(`when source isn't specified, uses filename`, () => {
expect(
preset.custom({
meta: {filename: path.join(__dirname, 'custom-preset.js'), existingContent: ''},
options: {input: 'abc'},
})
).toEqual('Whole module export with input: abc')
})

test('errors for non-existent source file', () => {
expect(() =>
preset.custom({
meta: {filename: __filename, existingContent: ''},
options: {source: './does-not-exist.ts', export: 'getText', input: 'abc'},
options: {source: './does-not-exist.ts'},
})
).toThrowError(/Source path doesn't exist: .*does-not-exist.ts/)
).toThrowError(/Source path is not a file: .*does-not-exist.ts/)
})

test('errors if directory passed as source', () => {
expect(() =>
preset.custom({
meta: {filename: __filename, existingContent: ''},
options: {source: '__tests__'},
})
).toThrowError(/Source path is not a file: .*__tests__/)
})

test('errors for non-existent export', () => {
expect(() =>
preset.custom({
meta: {filename: __filename, existingContent: ''},
options: {source: './custom-preset.js', export: 'doesNotExist', input: 'abc'},
})
).toThrowError(/Couldn't find export doesNotExist from .*custom-preset.js - got undefined/)
})

test('errors for export with wrong type', () => {
expect(() =>
preset.custom({
meta: {filename: __filename, existingContent: ''},
options: {source: './invalid-custom-preset.js', input: 'abc'},
})
).toThrowError(/Couldn't find export function from .*invalid-custom-preset.js - got object/)
})

test('can require module first', () => {
expect(() =>
preset.custom({
meta: {filename: __filename, existingContent: ''},
options: {source: './custom-preset.js', require: 'thismoduledoesnotexist'},
})
).toThrowError(/Cannot find module 'thismoduledoesnotexist' from 'src\/presets\/custom.ts'/)
})
28 changes: 24 additions & 4 deletions packages/eslint-plugin-codegen/src/presets/custom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,34 @@ import type {Preset} from '.'
*
* `<!-- codegen:start {preset: custom, source: ./lib/my-custom-preset.js, export: jsonPrinter, myCustomProp: hello}`
*
* @param source Relative path to the module containing the custom preset
* @param source Relative path to the module containing the custom preset.
* @param export The name of the export. If omitted, the module itself should be a preset function.
* @param require A module to load before `source`. If not set, defaults to `ts-node/register/transpile-only` for typescript sources.
* @param dev Set to `true` to clear the require cache for `source` before loading. Allows editing the function without requiring an IDE reload.
*/
export const custom: Preset<{source: string; export?: string} & Record<string, any>> = ({meta, options}) => {
const sourcePath = path.join(path.dirname(meta.filename), options.source)
export const custom: Preset<
{
source?: string
export?: string
require?: string
dev?: boolean
} & Record<string, unknown>
> = ({meta, options}) => {
const sourcePath = options.source ? path.join(path.dirname(meta.filename), options.source) : meta.filename
if (!fs.existsSync(sourcePath) || !fs.statSync(sourcePath).isFile()) {
throw Error(`Source path doesn't exist: ${sourcePath}`)
throw Error(`Source path is not a file: ${sourcePath}`)
}

const requireFirst = options.require || (sourcePath.endsWith('.ts') ? 'ts-node/register/transpile-only' : undefined)
if (requireFirst) {
require(requireFirst)
}

if (options.dev) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete require.cache[sourcePath]
}

// eslint-disable-next-line @typescript-eslint/no-var-requires
const sourceModule = require(sourcePath)
const func = options.export ? sourceModule[options.export] : sourceModule
Expand Down