Skip to content

Commit

Permalink
feat: Pass options to all transforms
Browse files Browse the repository at this point in the history
  • Loading branch information
Sidnioulz committed Sep 7, 2023
1 parent 2369623 commit d984bc8
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 41 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ yarn add -D vue-sfcmod

## Command Line Usage

`npx vue-sfcmod <path> -t <transformation> --params [transformation params] [...additional options]`
`npx vue-sfcmod <path> -t <transformation> --custom-opt [custom value, else customOpt will be true] [...add as many custom opts as wanted]`

- `transformation` (required) - path to a module exporting a transformation function (JS/TS only) or an object with three transformation functions (`script` key for JS/TS, `template` for HTML and `style` for CSS)
- `path` (required) - files or directory to transform.
- `--params` (optional) - additional parameters passed to the transformation function

Any CLI option you pass apart from `--version`, `--help` and `-t` will be passed to the script, style and template transformation functions in an object. For instance, if you pass `--classes-to-add="foo bar"`, you'll receive `{ classesToAdd: 'foo bar' }` as a third argument to your transformation functions.

## Programmatic API

- `runTransformation(fileInfo, transformation, params)`
- `runTransformation(fileInfo, transformation, options)`

## Known Limitations

Expand Down Expand Up @@ -60,12 +61,14 @@ When strings are passed to [`style` attributes](https://vuejs.org/guide/essentia
### Template

- [x] Support `<template>` [#15](https://github.com/Sidnioulz/vue-sfcmod/issues/15)
- [x] Support passing parameters to template transformations
- [ ] _ongoing_ - Add an API to search for, edit, remove and inject nodes in template ASTs
- [ ] Allow interpreting and modding JS expressions inside `<template>`

### Style

- [ ] Support `<style>` [#16](https://github.com/Sidnioulz/vue-sfcmod/issues/16)
- [ ] Support passing parameters to style transformations
- [ ] Support :global, :slotted, etc
- [ ] Support PostCSS and SCSS style tags

Expand Down
18 changes: 11 additions & 7 deletions bin/vue-sfcmod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { globbySync } from 'globby'
import yargs from 'yargs'

import runTransformation from '~/runTransformation'
import type { Options } from '~/types/TransformationOptions'

const debug = createDebug('vue-sfcmod')
// eslint-disable-next-line no-console
Expand All @@ -16,18 +17,14 @@ const log = console.log.bind(console)
const {
_: files,
transformation: transformationName,
params,
...allOptions
} = yargs()
.usage('Usage: $0 [file pattern]')
.usage('Usage: $0 [file pattern] -t [transformation]')
.option('transformation', {
alias: 't',
type: 'string',
describe: 'Name or path of the transformation module',
})
.option('params', {
alias: 'p',
describe: 'Custom params to the transformation',
})
.demandOption('transformation')
.help()
.parseSync(process.argv.slice(2))
Expand All @@ -45,6 +42,13 @@ async function main() {
const resolvedPaths = globbySync(files as string[])
const transformationModule = await loadTransformationModule(transformationName)

const params: Options = {}
for (const optKey of Object.keys(allOptions)) {
if (optKey !== 't' && optKey !== '$0' && !optKey.match(/-[a-z]/)) {
params[optKey] = allOptions[optKey]
}
}

log(`Processing ${resolvedPaths.length} files…`)

for (const p of resolvedPaths) {
Expand All @@ -54,7 +58,7 @@ async function main() {
source: fs.readFileSync(p).toString(),
}
try {
const result = runTransformation(fileInfo, transformationModule, params as object)
const result = runTransformation(fileInfo, transformationModule, params)
fs.writeFileSync(p, result)
} catch (e) {
// eslint-disable-next-line no-console
Expand Down
43 changes: 43 additions & 0 deletions examples/params/Input.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<template>
<article>
<h1>Clara Campoamor Rodríguez</h1>
<h2>Early days</h2>
Clara Campoamor Rodríguez was born on 12 February 1888 in Madrid, Spain to a working-class
family, she began working as a seamstress at age 13, but continued to study part-time on the
side, eventually seeking to pass the test that would guarantee her entry into law school. In the
interim, she worked her way up through a number of government positions, first with the Post
Office in San Sebastián in 1909, then as a typing teacher back in Madrid in 1914.
<h2>Political debut</h2>
In addition to her job as a teacher, Campoamor became involved in the Madrid political scene
through a second job as a journalist at the newspaper La Tribuna, where she got in touch with
influential feminine figures of the time, such as Carmen de Burgos and Eva Nelken. These
acquaintances led Clara Campoamor to join and collaborate with various feminist associations and
to write political commentary.
<h2>Law practice</h2>
After successfully passing the law school entrance exam and entering the University of Madrid
School of Law, Campoamor continued to work multiple jobs until she earned her degree in 1924,
aged 36, and entered legal practice. Campoamor was the second woman to ever incorporate the
Madrid Bar Association, the first one to defend a case before the Spanish High Court, and one of
the first to represent Spain in the League of Nations. Her private practice specialized in
issues affecting women, including paternity cases and marital law. Campoamor successfully
advocated in 1927 for improvements to the child labor laws and electoral law changes. When it
became legal for women to run for the Constituent Assembly that would write a new constitution
in 1931, she stood for a seat and was elected despite her inability to vote in the election.
<h2>An advocate for women's suffrage</h2>
On October of the same year, and using her position in the constituent assembly, she became the
first woman to address it, in a memorable speech warning the male members of the assembly that
their continued exclusion of women from voting was a violation of natural law: “To all deputies:
I am a citizen before. And I reckon it would be a tremendous political mistake not to allow
women to exercise this right, women that look up to and trust you; women that, similarly to the
French Revolution, will undoubtedly be a new power to our laws, and you only have to open their
way”. Campoamor affirmed that a Republic could not be built without half the citizenship of the
country and thus, women needed to be given the right to vote.
</article>
</template>

<script lang="ts" setup>
defineOptions({
name: 'Biography',
inheritAttrs: false,
})
</script>
48 changes: 48 additions & 0 deletions examples/params/transformation.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// ------------------------------------------------------------- //
// This example shows how parameters can be passed to a transformer.
// ------------------------------------------------------------- //

function template(ast, api, options) {
const optKeys = Object.keys(options)
if (optKeys.length === 0) {
process.stderr.write(`Pass options to this example to test its behaviour, e.g. :
yarn example params --root-heading 2
This example will read \`params.rootHeading\` to exploit the value.
`)
} else {
process.stderr.write(`
Received options:
${JSON.stringify(options, null, 2)}
`)

if (options.rootHeading) {
const newTopLevel = options.rootHeading
if (typeof newTopLevel !== 'number' || newTopLevel < 1 || newTopLevel > 4) {
throw new Error('Invalid option --root-heading: value must be a number between 1 and 4.')
}

const headings = api.exploreAst(
ast,
({ tag, type }) => tag && tag.match(/h[1-6]/) && type === 1,
)
const oldTopLevel = headings
.map((h) => Number(h.tag.replace('h', '')))
.reduce((min, n) => Math.min(min, n), 6)
const topLevelDiff = newTopLevel - oldTopLevel

headings.forEach((h) => {
const currentLevel = Number(h.tag.replace('h', ''))
h.tag = `h${currentLevel + topLevelDiff}`
})
}
}

return ast
}

module.exports = {
template,
}
32 changes: 5 additions & 27 deletions examples/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ printError() {
}

if [ "$#" -eq 0 ]; then
printError "Usage:\n\t${0} [Path to transformation] [Path to Vue files]\n\tor\n\t${0} [Example folder]"
printError "Usage:\n\n\tyarn example [example name] [options]\n"
fi

# Sanitise transform arg
Expand All @@ -17,34 +17,12 @@ if [ ! -f "$transformPath" ]; then
if [ -f "examples/${transformPath}/transformation.cjs" ]; then
transformPath="examples/${transformPath}/transformation.cjs"
else
printError "Transform not found: $transformPath"
printError "Example not found: $transformPath"
fi
fi

# Examples are used
if [ "$#" -eq 1 ] && [ -d "examples/$1" ]; then
isExample=true
inputPaths="examples/$1/Input.*"
inputPaths="examples/$1/Input.*"

# Paths to source files are used
else
isExample=false
# Compute all file paths. Vue-codemod doesn't honour --extensions
# so we do it manually by only keeping js/vue files.
allFiles=()
for path in "${@:2}"
do
# https://stackoverflow.com/a/63969005
IFS=$'\n'
allFiles+=($(find $path -name "*vue"))
unset IFS
done

inputPaths=$(printf '%q ' "${allFiles[@]}")
fi

yarn cli $inputPaths -t $transformPath
yarn cli $inputPaths -t $transformPath ${@:2}
yarn format:staged $inputPaths
if [ isExample ]; then
git diff $inputPaths
fi
git diff $inputPaths
3 changes: 2 additions & 1 deletion src/runTransformation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import type { StyleTransformation } from '~/types/StyleTransformation'
import type { TemplateTransformation } from '~/types/TemplateTransformation'
import type { TransformationBlock } from '~/types/TransformationBlock'
import type { TransformationModule } from '~/types/TransformationModule'
import type { Options } from '~/types/TransformationOptions'
import debug from '~/utils/debug'
import { normaliseTransformationModule } from '~/utils/normaliseTransformationModule'

export default function runTransformation(
fileInfo: FileInfo,
transformationModule: TransformationModule,
params: object = {},
params: Options = {},
) {
const transformation = normaliseTransformationModule(transformationModule)

Expand Down
3 changes: 2 additions & 1 deletion src/transformCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import getParser from 'jscodeshift/src/getParser.js'
import processTransformResult from '~/processTransformResult'
import type { JSTransformation } from '~/types/JSTransformation'
import type { TransformationBlock } from '~/types/TransformationBlock'
import type { Options } from '~/types/TransformationOptions'
import debug from '~/utils/debug'

export default function transformCode(
transformation: JSTransformation,
descriptor: TransformationBlock,
path: string,
params: object,
params: Options,
): boolean {
debug('Running jscodeshift transform')

Expand Down
4 changes: 3 additions & 1 deletion src/transformTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import * as TemplateAPI from '~/template/api'
import { stringify } from '~/template/stringify'
import type { TemplateTransformation } from '~/types/TemplateTransformation'
import type { TransformationBlock } from '~/types/TransformationBlock'
import type { Options } from '~/types/TransformationOptions'
import debug from '~/utils/debug'
import error from '~/utils/error'

export default function transformTemplate(
transformation: TemplateTransformation,
descriptor: TransformationBlock,
path: string,
params: Options,
): boolean {
debug('Running template transform')

Expand All @@ -28,7 +30,7 @@ export default function transformTemplate(
)
}

const out = stringify(transformation(result.ast, TemplateAPI))
const out = stringify(transformation(result.ast, TemplateAPI, params))

return processTransformResult(descriptor, out)
}
7 changes: 6 additions & 1 deletion src/types/TemplateTransformation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { RootNode } from '@vue/compiler-core'

import * as TemplateAPI from '~/template/api'
import type { Options } from '~/types/TransformationOptions'

export type TemplateTransformation = (ast: RootNode, api: typeof TemplateAPI) => RootNode
export type TemplateTransformation = (
ast: RootNode,
api: typeof TemplateAPI,
options: Options,
) => RootNode
3 changes: 3 additions & 0 deletions src/types/TransformationOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface Options {
[option: string]: unknown
}

0 comments on commit d984bc8

Please sign in to comment.