import hyntax from "hyntax" ; import { SFCBlock , SFCCustomBlock } from "@vue/component-compiler-utils/dist/parse" ; import { split , first , last , pipe } from "../utils" ; export const regexp = { template : /\/\*([\s\*]+)?@VueLiteralCompiler([\s]+)?Template([^`]+)?`[^`]+`([\s]+)?;?/ , styles : /\/\*\*([\s\*]+)?@VueLiteralCompiler([\s]+)?Styles([\s\*]+)?\*\/([^`]+)?`[^`]+`([\s]+)?;?/g , customBlock : /\/\*([\s\*]+)?@VueLiteralCompiler([\s]+)?Custom Block([\s]+)?\*\/([^`]+)?`[^`]+`([\s]+)?;?/g , langAttr : /lang=[\'\"]([a-z]+:?)[\'\"]/ , } ; export function matchJSDocDirective ( fileContent , rgx ) { let matches = [ ] ; let regexpArr ; while ( ( regexpArr = rgx . exec ( fileContent ) ) !== null ) { const match = regexpArr [ 0 ] ; const content = getBacktildesContent ( match ) ; const start = regexpArr . index ; const end = start + match . length ; matches . push ( { content , start , end } ) ; } return matches ; } export function matchJSDocTemplateDirective ( fileContent , rgx ) { let matches = [ ] , start = 0 , end = 0 ; const regexpArr = rgx . exec ( fileContent ) ; if ( regexpArr === null ) return matches ; const match = regexpArr [ 0 ] ; start = regexpArr . index ; end = start + match . length ; let content = getBacktildesContent ( match ) ; const assignmentStatement = regexpArr [ regexpArr . length - 2 ] ; if ( templateIsFunctional ( assignmentStatement ) ) { const variableMatcher = /\${([^}]+:?)}/ ; const param = getParameter ( assignmentStatement ) ; if ( param . length === 0 ) { throw new Error ( "Vue Literal Compiler Error:\n" + "Functional Templates with no parameters (() => any) are not supported.\n" + "Either pass a single parameter using the fat arrow syntax or use a simple assignment template.\n" + "\nExamples of supported template signatures are:\n" + "1. const template = `...`\n" + "2. const template = app => `...`\n" + "3. const template = (app) => `...`\n" + "4. const template = (app:App) => `...`\n" + "\n- A Functional template can only have one parameter which represents the instance of the Vue component.\n" + "- Only Fat Arrow Syntax is supported for functional templates\n\n" ) ; } const contentToken = hyntax . tokenize ( content ) . tokens as HyntaxToken [ ] ; const variableTokens = fetchVariableTokens ( contentToken , variableMatcher ) ; const dd = variableTokens . map ( vtoken => resolveContent ( vtoken , param ) ) ; dd . map ( d => content = replaceContentAtPosition ( content , d . orignal , d . replaced ) ) ; } matches . push ( { content , start , end } ) ; return matches ; function getParameter ( assignmentStatement ) { const pipeParameter = pipe < string , string > ( split ( "=>" ) , first , split ( "=" ) , last , split ( ":" ) , first , split ( "(" ) , last , split ( ")" ) , first ) ; return pipeParameter ( assignmentStatement ) ; } function templateIsFunctional ( assignmentStatement ) { return assignmentStatement . indexOf ( "=>" ) > - 1 ; } function replaceContentAtPosition ( content , token , replaced ) { return content . replace ( token . content , replaced ) ; } function fetchVariableTokens ( tokens , variableMatcher ) { const variableTokens = [ ] ; for ( let token of tokens ) { switch ( token . type ) { case "token:text" : const cd = token . content . match ( variableMatcher ) ; if ( cd === null ) break ; variableTokens . push ( token ) ; break ; case "token:attribute-value" : const cd2 = token . content . match ( variableMatcher ) ; if ( cd2 === null ) break ; variableTokens . push ( token ) ; break ; default : break ; } } return variableTokens ; } function resolveContent ( token , param ) { let replaced = token . content ; const openLiteralCurlyBracesPosition = token . content . indexOf ( "${" ) ; if ( openLiteralCurlyBracesPosition > - 1 ) { if ( token . type === "token:text" ) { replaced = replaceAtPosition ( replaced , openLiteralCurlyBracesPosition , "{" ) ; const closingLiteralCurlyBracesPosition = replaced . lastIndexOf ( "}" ) ; replaced = replaceAtPosition ( replaced , closingLiteralCurlyBracesPosition , "}}" ) ; } else { replaced = replaceAtPosition ( replaced , openLiteralCurlyBracesPosition , "" ) ; replaced = replaceAtPosition ( replaced , openLiteralCurlyBracesPosition , "" ) ; const closingLiteralCurlyBracesPosition = replaced . lastIndexOf ( "}" ) ; replaced = replaceAtPosition ( replaced , closingLiteralCurlyBracesPosition , "" ) ; } replaced = replaced . replace ( "<any>" , "" ) ; const paramCallRegx = new RegExp ( param + "([\s]+)?." ) ; replaced = replaced . split ( " " ) . map ( d => d . replace ( paramCallRegx , "" ) ) . join ( " " ) ; } return { orignal : token , replaced , } ; } function replaceAtPosition ( str , index , replace ) { return str . substring ( 0 , index ) + replace + str . substring ( index + 1 ) ; } } export function normalizeStyles ( stylesWithTags , start , end ) { let isScoped = undefined ; const stylesWithHeader = stylesWithTags . trimRight ( ) . split ( /<\/([\s]+)?style([\s]+)?>/g ) . filter ( v => v ) ; let startPositionOfEachStyleTag = start ; const styles = stylesWithHeader . map ( s => { const block = normalizeStyle ( s , startPositionOfEachStyleTag ) ; if ( block . scoped ) isScoped = true ; startPositionOfEachStyleTag = block . end ; return block ; } ) ; if ( styles . length === 1 && styles [ 0 ] . start === 0 && styles [ 0 ] . end === 0 ) { styles [ 0 ] . start = start ; styles [ 0 ] . end = end ; } return { isScoped , styles , start , end , } ; function normalizeStyle ( styleWithHeader , start ) { let lang = undefined ; let scoped = undefined ; const attrs = { } ; const styleHeaderRegexp = /<([^>]+)?>/g ; let styleHeader = "" , compStart = 0 , compEnd = 0 ; const styleDeclaration = styleWithHeader . replace ( styleHeaderRegexp , ( match , ... args ) => { compStart = args [ 1 ] + start ; compEnd = compStart + styleWithHeader . length ; styleHeader = match ; return "" ; } ) ; if ( styleHeader . indexOf ( "scoped" ) > - 1 ) { scoped = true ; attrs . scoped = true ; } const styleMatch = styleHeader . match ( regexp . langAttr ) ; if ( styleMatch ) { lang = styleMatch [ 1 ] ; attrs . lang = lang ; } return { type : "style" , content : styleDeclaration , scoped , lang , start : compStart , end : compEnd , attrs , src : undefined , map : undefined , module : undefined , } ; } } export function normalizeTemplate ( templateMarkup , start , end , isScoped ) { const { content , attrs } = removeWrappingTemplateTagsIfAny ( templateMarkup , start , end ) ; return { type : "template" , attrs , content , start , end , scoped : isScoped , lang : attrs . lang , map : undefined , module : undefined , src : undefined , } ; function removeWrappingTemplateTagsIfAny ( templateMarkup , start , end ) { const attrs = { } ; templateMarkup = templateMarkup . trim ( ) ; if ( templateMarkup . length === 0 ) return { content : "" , attrs } ; const firstTagRegexp = /<((.|\n)*?):?>/ ; const firstTagRegexpArr = templateMarkup . match ( firstTagRegexp ) ; if ( firstTagRegexpArr === null ) { throw new Error ( "\n<template> tag not found.\n" + "Usage of template tag is only optional if you're writing html.\n" + "Are you trying to use a different template lang like pug or jade?\n" + "If so specify the template language you're using:\n" + "Example:\n" + "<template lang=\"jade\">\n" + "...\n" + "</template>" ) ; } else { const tagString = firstTagRegexpArr [ 1 ] ; const tagName = tagString . split ( " " ) [ 0 ] ; if ( tagName === "template" ) { const match = firstTagRegexpArr [ 0 ] ; let tempArr = templateMarkup . split ( match ) ; tempArr . shift ( ) ; templateMarkup = tempArr . join ( match ) ; tempArr = templateMarkup . split ( "</" ) ; tempArr . pop ( ) ; templateMarkup = tempArr . join ( "</" ) ; const langAttrMatched = tagString . match ( regexp . langAttr ) ; if ( langAttrMatched ) attrs . lang = langAttrMatched [ 1 ] ; if ( tagString . indexOf ( "functional" ) > - 1 ) attrs . functional = true ; } return { content : templateMarkup , attrs , } ; } } } export function normalizeCustomBlocks ( customBlocksDeclartion ) { return customBlocksDeclartion . map ( c => normalize ( c ) ) ; function normalize ( customBlock ) { let type = "" ; const attrs = { } ; const customBlockTypeRegex = /<(([\s]+)?[^>]+:?)>/ ; const headerless = customBlock . content . replace ( customBlockTypeRegex , ( match , ... args ) => { const inner = args [ 0 ] as string ; type = inner . replace ( regexp . langAttr , ( _ , ... args ) => { attrs . lang = args [ 0 ] ; return "" ; } ) ; return "" ; } ) ; const endRegexp = new RegExp ( "<\/([\s]+)?" + type . trim ( ) + "([\s]+)?>" ) ; const content = headerless . replace ( endRegexp , "" ) ; return { content , attrs , type , start : customBlock . start , end : customBlock . end , map : undefined , } ; } } export function normalizeScripts ( modifiedFile ) { return { type : "script" , content : modifiedFile , lang : "ts" , attrs : { lang : "ts" } , start : 0 , end : modifiedFile . length } ; } function getBacktildesContent ( match ) { const startLiteralIndex = match . indexOf ( "`" ) ; const endDirective = match . indexOf ( "`" , startLiteralIndex + 1 ) ; return match . slice ( startLiteralIndex + 1 , endDirective ) ; } export interface LiteralMatch { matches : MatchedDeclaration [ ] ; modified : string ; } export interface MatchedDeclaration { content : string ; start : number ; end : number ; } export interface NormalizedStyles { isScoped : true | undefined ; styles : SFCBlock [ ] ; start : number ; end : number ; } interface HyntaxToken { type : "token:text" | "token:close-tag" | "token:open-tag-end" | "token:open-tag-start" | "token:attribute-value" ; content : string ; startPosition : number ; endPosition : number ; } interface ResolvedLiteral { orignal : HyntaxToken ; replaced : string ; } export interface DeclarationsArePresent { template : boolean ; styles : boolean ; customBlock : boolean ; } export function removeDeclarations ( str , isPresent ) { if ( isPresent . template ) str = str . replace ( regexp . template , "" ) ; if ( isPresent . styles ) str = str . replace ( regexp . styles , "" ) ; if ( isPresent . customBlock ) str = str . replace ( regexp . customBlock , "" ) ; return str ; }