Skip to content

Commit 040e0b7

Browse files
committed
feat: add codetransformer.addLimiter codemod
1 parent adfc592 commit 040e0b7

3 files changed

Lines changed: 177 additions & 0 deletions

File tree

src/code_transformer/main.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import type {
2828
EnvValidationNode,
2929
BouncerPolicyNode,
3030
ValidatorNode,
31+
LimiterNode,
3132
MixinDefinition,
3233
ControllerMethodNode,
3334
} from '../types/code_transformer.ts'
@@ -672,6 +673,53 @@ export class CodeTransformer {
672673
await file.save()
673674
}
674675

676+
async addLimiter(definition: LimiterNode) {
677+
const directories = this.getDirectories()
678+
const filePath = `${directories.start}/${definition.limiterFileName}`
679+
680+
/**
681+
* Get the limiter file URL
682+
*/
683+
const limiterFileUrl = join(this.#cwdPath, `./${filePath}`)
684+
let file = this.project.getSourceFile(limiterFileUrl)
685+
686+
/**
687+
* Try to load the file from disk if not already in the project
688+
*/
689+
if (!file) {
690+
try {
691+
file = this.project.addSourceFileAtPath(limiterFileUrl)
692+
} catch {
693+
// File does not exist on disk, we will create it
694+
}
695+
}
696+
697+
/**
698+
* If the file does not exist, create it
699+
*/
700+
if (!file) {
701+
file = this.project.createSourceFile(limiterFileUrl, definition.contents)
702+
file.formatText(this.#editorSettings)
703+
await file.save()
704+
return
705+
}
706+
707+
/**
708+
* Check if the export already exists
709+
*/
710+
const existingExport = file.getVariableDeclaration(definition.exportName)
711+
if (existingExport) {
712+
return
713+
}
714+
715+
/**
716+
* Add the limiter to the existing file
717+
*/
718+
file.addStatements(`\n${definition.contents}`)
719+
file.formatText(this.#editorSettings)
720+
await file.save()
721+
}
722+
675723
async addModelMixins(modelFileName: string, mixins: MixinDefinition[]) {
676724
const directories = this.getDirectories()
677725
const filePath = `${directories.models}/${modelFileName}`

src/types/code_transformer.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@ export type ValidatorNode = {
114114
contents: string
115115
}
116116

117+
export type LimiterNode = {
118+
limiterFileName: string
119+
exportName: string
120+
contents: string
121+
}
122+
117123
export type MixinDefinition = {
118124
name: string
119125
args?: string[]

tests/code_transformer.spec.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1425,6 +1425,129 @@ test.group('Code Transformer | addValidator', (group) => {
14251425
})
14261426
})
14271427

1428+
test.group('Code Transformer | addLimiter', (group) => {
1429+
group.each.setup(async ({ context }) => setupFakeAdonisproject(context.fs))
1430+
1431+
test('add limiter to a non-existing limiter file', async ({ assert, fs }) => {
1432+
const transformer = new CodeTransformer(fs.baseUrl)
1433+
1434+
await transformer.addLimiter({
1435+
exportName: 'apiLimiter',
1436+
limiterFileName: 'limiter.ts',
1437+
contents: dedent`
1438+
export const apiLimiter = limiter.define('api', () => {
1439+
return limiter.allowRequests(10).every('1 minute')
1440+
})
1441+
`,
1442+
})
1443+
1444+
const file = await fs.contents('start/limiter.ts')
1445+
assert.snapshot(file).matchInline(`
1446+
"export const apiLimiter = limiter.define('api', () => {
1447+
return limiter.allowRequests(10).every('1 minute')
1448+
})
1449+
"
1450+
`)
1451+
})
1452+
1453+
test('add limiter to existing limiter file with other exports', async ({ assert, fs }) => {
1454+
const transformer = new CodeTransformer(fs.baseUrl)
1455+
await fs.create(
1456+
'start/limiter.ts',
1457+
dedent`
1458+
export const globalLimiter = limiter.define('global', () => {
1459+
return limiter.allowRequests(100).every('1 minute')
1460+
})
1461+
`
1462+
)
1463+
1464+
await transformer.addLimiter({
1465+
exportName: 'apiLimiter',
1466+
limiterFileName: 'limiter.ts',
1467+
contents: dedent`
1468+
export const apiLimiter = limiter.define('api', () => {
1469+
return limiter.allowRequests(10).every('1 minute')
1470+
})
1471+
`,
1472+
})
1473+
1474+
const file = await fs.contents('start/limiter.ts')
1475+
assert.snapshot(file).matchInline(`
1476+
"export const globalLimiter = limiter.define('global', () => {
1477+
return limiter.allowRequests(100).every('1 minute')
1478+
})
1479+
1480+
export const apiLimiter = limiter.define('api', () => {
1481+
return limiter.allowRequests(10).every('1 minute')
1482+
})
1483+
"
1484+
`)
1485+
})
1486+
1487+
test('skip adding limiter when it already exists', async ({ assert, fs }) => {
1488+
const transformer = new CodeTransformer(fs.baseUrl)
1489+
await fs.create(
1490+
'start/limiter.ts',
1491+
dedent`
1492+
export const apiLimiter = limiter.define('api', () => {
1493+
return limiter.allowRequests(5).every('1 minute')
1494+
})
1495+
`
1496+
)
1497+
1498+
await transformer.addLimiter({
1499+
exportName: 'apiLimiter',
1500+
limiterFileName: 'limiter.ts',
1501+
contents: dedent`
1502+
export const apiLimiter = limiter.define('api', () => {
1503+
return limiter.allowRequests(10).every('1 minute')
1504+
})
1505+
`,
1506+
})
1507+
1508+
const file = await fs.contents('start/limiter.ts')
1509+
assert.snapshot(file).matchInline(`
1510+
"export const apiLimiter = limiter.define('api', () => {
1511+
return limiter.allowRequests(5).every('1 minute')
1512+
})"
1513+
`)
1514+
})
1515+
1516+
test('use custom start directory from adonisrc.ts', async ({ assert, fs }) => {
1517+
await fs.create(
1518+
'adonisrc.ts',
1519+
dedent`
1520+
import { defineConfig } from '@adonisjs/core/app'
1521+
1522+
export default defineConfig({
1523+
directories: {
1524+
start: 'boot',
1525+
}
1526+
})`
1527+
)
1528+
1529+
const transformer = new CodeTransformer(fs.baseUrl)
1530+
1531+
await transformer.addLimiter({
1532+
exportName: 'apiLimiter',
1533+
limiterFileName: 'limiter.ts',
1534+
contents: dedent`
1535+
export const apiLimiter = limiter.define('api', () => {
1536+
return limiter.allowRequests(10).every('1 minute')
1537+
})
1538+
`,
1539+
})
1540+
1541+
const file = await fs.contents('boot/limiter.ts')
1542+
assert.snapshot(file).matchInline(`
1543+
"export const apiLimiter = limiter.define('api', () => {
1544+
return limiter.allowRequests(10).every('1 minute')
1545+
})
1546+
"
1547+
`)
1548+
})
1549+
})
1550+
14281551
test.group('Code Transformer | addModelMixins', (group) => {
14291552
group.each.setup(async ({ context }) => setupFakeAdonisproject(context.fs))
14301553

0 commit comments

Comments
 (0)