riot/compiler

View on GitHub
src/generators/javascript/index.js

Summary

Maintainability
A
2 hrs
Test Coverage
import {
  addComponentInterfaceToExportedObject,
  createDefaultExportFromLegacySyntax,
  extendTagProperty,
  filterNonExportDefaultStatements,
  findAllExportNamedDeclarations,
  findAllImportDeclarations,
  findComponentInterface,
  findExportDefaultStatement,
  getProgramBody,
} from './utils.js'
import addLinesOffset from '../../utils/add-lines-offset.js'
import generateAST from '../../utils/generate-ast.js'
import getPreprocessorTypeByAttribute from '../../utils/get-preprocessor-type-by-attribute.js'
import isEmptySourcemap from '../../utils/is-empty-sourcemap.js'
import { isNil } from '@riotjs/util/checks.js'
import { isThisExpressionStatement } from '../../utils/ast-nodes-checks.js'
import preprocess from '../../utils/preprocess-node.js'
import sourcemapToJSON from '../../utils/sourcemap-as-json.js'

/**
 * Generate the component javascript logic
 * @param   { Object } sourceNode - node generated by the riot compiler
 * @param   { string } source - original component source code
 * @param   { Object } meta - compilation meta information
 * @param   { AST } ast - current AST output
 * @returns { AST } the AST generated
 */
export default function javascript(sourceNode, source, meta, ast) {
  const preprocessorName = getPreprocessorTypeByAttribute(sourceNode)
  const javascriptNode = addLinesOffset(
    sourceNode.text.text,
    source,
    sourceNode,
  )
  const { options } = meta
  const preprocessorOutput = preprocess('javascript', preprocessorName, meta, {
    ...sourceNode,
    text: javascriptNode,
  })
  const inputSourceMap = sourcemapToJSON(preprocessorOutput.map)
  const generatedAst = generateAST(preprocessorOutput.code, {
    sourceFileName: options.file,
    inputSourceMap: isEmptySourcemap(inputSourceMap) ? null : inputSourceMap,
  })
  const generatedAstBody = getProgramBody(generatedAst)
  const exportDefaultNode = findExportDefaultStatement(generatedAstBody)
  const isLegacyRiotSyntax = isNil(exportDefaultNode)
  const outputBody = getProgramBody(ast)
  const componentInterface = findComponentInterface(generatedAstBody)

  // throw in case of mixed component exports
  if (exportDefaultNode && generatedAstBody.some(isThisExpressionStatement))
    throw new Error(
      'You can\t use "export default {}" and root this statements in the same component',
    )

  // add to the ast the "private" javascript content of our tag script node
  outputBody.unshift(
    ...// for the legacy riot syntax we need to move all the import and (named) export statements outside of the function body
    (isLegacyRiotSyntax
      ? [
          ...findAllImportDeclarations(generatedAstBody),
          ...findAllExportNamedDeclarations(generatedAstBody),
        ]
      : // modern riot syntax will hoist all the private stuff outside of the export default statement
        filterNonExportDefaultStatements(generatedAstBody)),
  )

  // create the public component export properties from the root this statements
  if (isLegacyRiotSyntax)
    extendTagProperty(
      ast,
      createDefaultExportFromLegacySyntax(generatedAstBody),
    )

  // convert the export default adding its content to the component property exported
  if (exportDefaultNode) extendTagProperty(ast, exportDefaultNode)

  return componentInterface
    ? // add the component interface to the component object exported
      addComponentInterfaceToExportedObject(ast, componentInterface)
    : ast
}