fix: Don't allow duplicate injections of import statements and root options by plugins (#1774)

* feat(cli): Don't allow duplicate api.injectImports

* feat(cli): Don't allow duplicate api.injectRootOptions

* chore(cli): Added tests for duplicate injections
This commit is contained in:
Pavan Kumar Sunkara
2018-07-11 14:03:21 +02:00
committed by Guillaume Chau
parent 9f0bf082e6
commit 8eb7fc3a97
2 changed files with 77 additions and 6 deletions
+54 -1
View File
@@ -16,6 +16,8 @@ fs.writeFileSync(path.resolve(templateDir, 'entry.js'), `
import foo from 'foo'
new Vue({
p: p(),
baz,
render: h => h(App)
}).$mount('#app')
`.trim())
@@ -454,7 +456,58 @@ test('api: addEntryImport & addEntryInjection', async () => {
await generator.generate()
expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/import foo from 'foo'\s+import bar from 'bar'/)
expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/new Vue\({\s+foo,\s+bar,\s+render: h => h\(App\)\s+}\)/)
expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/new Vue\({\s+p: p\(\),\s+baz,\s+foo,\s+bar,\s+render: h => h\(App\)\s+}\)/)
})
test('api: addEntryDuplicateImport', async () => {
const generator = new Generator('/', { plugins: [
{
id: 'test',
apply: api => {
api.injectImports('main.js', `import foo from 'foo'`)
api.render({
'main.js': path.join(templateDir, 'entry.js')
})
}
}
] })
await generator.generate()
expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/^import foo from 'foo'\s+new Vue/)
})
test('api: addEntryDuplicateInjection', async () => {
const generator = new Generator('/', { plugins: [
{
id: 'test',
apply: api => {
api.injectRootOptions('main.js', 'baz')
api.render({
'main.js': path.join(templateDir, 'entry.js')
})
}
}
] })
await generator.generate()
expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/{\s+p: p\(\),\s+baz,\s+render/)
})
test('api: addEntryDuplicateNonIdentifierInjection', async () => {
const generator = new Generator('/', { plugins: [
{
id: 'test',
apply: api => {
api.injectRootOptions('main.js', 'p: p()')
api.render({
'main.js': path.join(templateDir, 'entry.js')
})
}
}
] })
await generator.generate()
expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/{\s+p: p\(\),\s+baz,\s+render/)
})
test('extract config files', async () => {
@@ -14,17 +14,30 @@ module.exports = function injectImportsAndOptions (source, imports, injections)
if (hasImports) {
const toImport = i => recast.parse(`${i}\n`).program.body[0]
const importDeclarations = []
let lastImportIndex = -1
recast.types.visit(ast, {
visitImportDeclaration ({ node }) {
lastImportIndex = ast.program.body.findIndex(n => n === node)
importDeclarations.push(node)
return false
}
})
// avoid blank line after the previous import
delete ast.program.body[lastImportIndex].loc
const newImports = imports.map(toImport)
const nonDuplicates = i => {
return !importDeclarations.some(node => {
const result = node.source.raw === i.source.raw && node.specifiers.length === i.specifiers.length
return result && node.specifiers.every((item, index) => {
return i.specifiers[index].local.name === item.local.name
})
})
}
const newImports = imports.map(toImport).filter(nonDuplicates)
ast.program.body.splice(lastImportIndex + 1, 0, ...newImports)
}
@@ -37,12 +50,17 @@ module.exports = function injectImportsAndOptions (source, imports, injections)
if (node.callee.name === 'Vue') {
const options = node.arguments[0]
if (options && options.type === 'ObjectExpression') {
const props = options.properties
const nonDuplicates = i => {
return !options.properties.slice(0, -1).some(p => {
return p.key.name === i[0].key.name &&
recast.print(p.value).code === recast.print(i[0].value).code
})
}
// inject at index length - 1 as it's usually the render fn
options.properties = [
...props.slice(0, props.length - 1),
...([].concat(...injections.map(toProperty))),
...props.slice(props.length - 1)
...options.properties.slice(0, -1),
...([].concat(...injections.map(toProperty).filter(nonDuplicates))),
...options.properties.slice(-1)
]
}
}