diff --git a/packages/gogocode-core/src/vue-core/generate.js b/packages/gogocode-core/src/vue-core/generate.js index 63cd28b..437c79e 100644 --- a/packages/gogocode-core/src/vue-core/generate.js +++ b/packages/gogocode-core/src/vue-core/generate.js @@ -1,123 +1,18 @@ -const indentString = require('indent-string'); -const jsGenerate = require('../js-core/generate') -const htmlGenerate = require('../html-core/serialize-node') -/** - * The following function is adapted from https://github.com/psalaets/vue-sfc-descriptor-to-string/blob/master/index.js - */ - -/** - * The MIT License (MIT) - * Copyright (c) 2018 Paul Salaets - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -module.exports = function toString(sfcDescriptor, options = {}) { - let { template, script, scriptSetup, styles = [], customBlocks = [], templateAst, scriptAst, scriptSetupAst } = sfcDescriptor; - if (templateAst && templateAst.node) { - template.content = htmlGenerate(templateAst.node); - } - if (scriptAst && scriptAst.node) { - script.content = jsGenerate(scriptAst.node); - } - - if (scriptSetupAst && scriptSetupAst.node) { - scriptSetup.content = jsGenerate(scriptSetupAst.node); - } - - const indents = Object.assign( - { - template: 2, - script: 0, - style: 0 - }, - options.indents - ); - - return ( - [template, script, scriptSetup, ...styles, ...customBlocks] - // discard blocks that don't exist - .filter((block) => block != null) - // sort blocks by source position - .sort((a, b) => a.start - b.start) - // figure out exact source positions of blocks - .map((block) => { - const openTag = makeOpenTag(block); - const closeTag = makeCloseTag(block); - - return Object.assign({}, block, { - openTag, - closeTag, - - startOfOpenTag: block.start - openTag.length, - endOfOpenTag: block.start, - - startOfCloseTag: block.end, - endOfCloseTag: block.end + closeTag.length - }); - }) - // generate sfc source - .reduce((sfcCode, block, index, array) => { - const first = index === 0; - - let newlinesBefore = 0; - - if (first) { - newlinesBefore = block.startOfOpenTag; - } else { - const prevBlock = array[index - 1]; - newlinesBefore = - block.startOfOpenTag - prevBlock.endOfCloseTag; - } - - newlinesBefore = newlinesBefore || 1 - - return ( - sfcCode + - '\n'.repeat(newlinesBefore) + - block.openTag + - indentString(block.content, indents[block.type] || 0) + - block.closeTag - ); - }, '') - ); -} - -function makeOpenTag(block) { - let source = '<' + block.type; - - source += Object.keys(block.attrs) - .sort() - .map((name) => { - const value = block.attrs[name]; - - if (value === true) { - return name; - } else { - return `${name}="${value}"`; - } - }) - .map((attr) => ' ' + attr) - .join(''); - - return source + '>'; -} - -function makeCloseTag(block) { - return `\n`; +const jsGenerate = require('../js-core/generate'); +const htmlGenerate = require('../html-core/serialize-node'); + +module.exports = function toString(sfcDescriptor) { + const { source } = sfcDescriptor + return ['template', 'script', 'scriptSetup'].reduce((acc, blockType) => { + const block = sfcDescriptor[blockType] + const ast = sfcDescriptor[blockType + 'Ast'] + if (!block || !ast?.node) { + return acc + } + const offset = source.length - acc.length + const transform = blockType === 'template' ? htmlGenerate : jsGenerate + const before = acc.slice(0, block.loc.start.offset - offset) + const after = acc.slice(block.loc.end.offset - offset, acc.length) + return before + transform(ast.node) + after + }, source) } diff --git a/packages/gogocode-core/test/G.vue.test.js b/packages/gogocode-core/test/G.vue.test.js index 7f32c1a..675d03c 100644 --- a/packages/gogocode-core/test/G.vue.test.js +++ b/packages/gogocode-core/test/G.vue.test.js @@ -26,7 +26,7 @@ const code = ` `, { parseOptions: { language: 'vue' }}) @@ -635,3 +635,146 @@ test('test vue parseoptions', () => { const res = ast.generate() expect(res.match('&&')).toBeTruthy() }) + +test('block sort order', () => { + const ast = $(` + + + +// + + + +`, + { parseOptions: { language: 'vue' } } + ); + const res = ast.generate(); + const expected = ` + + + +// + + + +`; + expect(res).toEqual(expected); +}); + +test('multiple block transformations', () => { + const ast = $(` + + + +{ + "en": { + "test" "Test" + } +} + + + + +`, + { parseOptions: { language: 'vue' } } + ); + ast.find('').replace('
$$$0
', '$$$0') + ast.find('').replace('export default {$$$0}', 'export default {\n name: "Component"\n};') + ast.find('').replace('import X from "Y"', 'import Y from "X"') + const res = ast.generate(); + const expected = ` + + + +{ + "en": { + "test" "Test" + } +} + + + + +`; + expect(res).toEqual(expected); +}); + + + +test('retains all newlines and comments', () => { + const ast = $(` + + +// + + + + + + +/** + * A comment + */ + + +`, + { parseOptions: { language: 'vue' } } + ); + const res = ast.generate(); + const expected = ` + + +// + + + + + + +/** + * A comment + */ + + +`; + expect(res).toEqual(expected); +}) +;