import hyntax from "hyntax" ; import { SFCBlock , SFCCustomBlock } from "@vue/component-compiler-utils/dist/parse" ; import { isArrowFunction , isImportDeclaration , isTaggedTemplateExpression , forEachChild , SourceFile , Node , LeftHandSideExpression , ImportDeclaration , ArrowFunction , TaggedTemplateExpression , SyntaxKind , ScriptTarget , createSourceFile } from "typescript" ; export const regexp = { langAttr : /lang=[\'\"]([a-z]+:?)[\'\"]/ , } ; export function tokenizeContent ( fileContent ) { const source = createSourceFile ( "vue.lit.ts" , fileContent , ScriptTarget . Latest ) ; const arrs = [ ] ; function callback ( node ) { switch ( node . kind ) { case SyntaxKind . ImportDeclaration : return arrs . push ( new ImportDefinition ( node ) ) ; case SyntaxKind . TaggedTemplateExpression : return arrs . push ( new TaggedLiteral ( node ) ) ; case SyntaxKind . ArrowFunction : return arrs . push ( new FatArrowLiteral ( node , fileContent ) ) ; } } ; function iterator ( sourceFile ) { sourceFile . forEachChild ( childNode => { callback ( childNode ) ; iterator ( childNode ) ; } ) ; } iterator ( source ) ; return arrs ; } export function removeDeclarations ( fileContent , { template , styles , customBlocks } ) { let templateMatch , styleMatches = [ ] , customBlockMatches = [ ] ; if ( template ) templateMatch = fileContent . substring ( template . start , template . end ) ; if ( styles . length > 0 ) styleMatches = styles . map ( style => fileContent . substring ( style . start , style . end ) ) ; if ( customBlocks . length > 0 ) customBlockMatches = customBlocks . map ( custom => fileContent . substring ( custom . start , custom . end ) ) ; if ( templateMatch ) fileContent = fileContent . replace ( templateMatch , "" ) ; for ( let style of styleMatches ) fileContent = fileContent . replace ( style , "" ) ; for ( let block of customBlockMatches ) fileContent = fileContent . replace ( block , "" ) ; return fileContent ; } export function resolveTemplateBindings ( parameter , fatArrowBody ) { const variableMatcher = /\${([^}]+:?)}/ ; const contentToken = hyntax . tokenize ( fatArrowBody ) . tokens as HyntaxToken [ ] ; const variableTokens = fetchVariableTokens ( contentToken , variableMatcher ) ; const dd = variableTokens . map ( vtoken => resolveContent ( vtoken , parameter ) ) ; dd . map ( d => fatArrowBody = replaceContentAtPosition ( fatArrowBody , d . orignal , d . replaced ) ) ; return fatArrowBody ; 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 replaceContentAtPosition ( content , token , replaced ) { return content . replace ( token . content , replaced ) ; } function replaceAtPosition ( str , index , replace ) { return str . substring ( 0 , index ) + replace + str . substring ( index + 1 ) ; } } export function normalizeStyle ( styleWithHeader , start , end ) { let content = "" ; let attrs = { } as Record < string , any > ; let lang = "" ; let scoped = undefined ; let styleHeader = "" ; styleWithHeader = styleWithHeader . trimRight ( ) . replace ( /<\/([\s]+)?style([\s]+)?>/g , "" ) ; const styleHeaderRegexp = /<([^>]+)?>/g ; content = styleWithHeader . replace ( styleHeaderRegexp , ( match , ... args ) => { styleHeader = args [ 0 ] ; 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 , scoped , lang , start , end , 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 normalizeCustomBlock ( { content , start , end } ) { let type = "" ; const attrs = { } ; const customBlockTypeRegex = /<(([\s]+)?[^>]+:?)>/ ; const headerless = 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 customBlockContent = headerless . replace ( endRegexp , "" ) ; return { content : customBlockContent , attrs , type , start , end , map : undefined , } ; } export function normalizeScript ( modifiedFile ) { return { type : "script" , content : modifiedFile , lang : "ts" , attrs : { lang : "ts" } , start : 0 , end : modifiedFile . length } ; } export interface MatchedDeclaration { content : string ; start : number ; end : number ; } export interface NormalizedStyles { isScoped : true | undefined ; styles : SFCBlock [ ] ; start : number ; end : number ; } export class TaggedLiteral { private _tag : LeftHandSideExpression & HasEscapedText ; body : string ; start : number ; end : number ; isFunctional = false ; constructor ( node ) { this . _tag = node . tag ; this . body = node . template . text ; this . start = node . pos ; this . end = node . end ; } get tag ( ) { return this . _tag . escapedText ; } } export class ImportDefinition { start : number ; end : number ; module : string ; imports : { name : string ; alias ? : string ; } [ ] = [ ] ; constructor ( node ) { this . module = node . moduleSpecifier . text ; this . start = node . pos ; this . end = node . end ; if ( node . importClause && node . importClause . namedBindings && node . importClause . namedBindings . elements ) { const elements = node . importClause . namedBindings . elements ; for ( let element of elements ) { if ( element . name && ! element . propertyName ) { this . imports . push ( { name : element . name . escapedText , } ) ; } else if ( element . propertyName && element . name ) { this . imports . push ( { name : element . propertyName . escapedText , alias : element . name . escapedText } ) ; } } } } } export class FatArrowLiteral { start : number ; end : number ; tag : string ; body : { text : string ; start : number ; end : number ; } | undefined = undefined ; parameters : string [ ] = [ ] ; isFunctional = false ; constructor ( node , fileContent ) { this . start = node . pos ; this . end = node . end ; this . tag = node . body . tag . escapedText ; if ( node . body . template ) { this . body = { text : fileContent . substring ( node . body . template . pos + 1 , node . body . template . end - 1 ) , start : node . body . template . pos , end : node . body . template . end , } ; } this . parameters = node . parameters . map ( p => p . name . escapedText ) ; } } export interface DeclarationsArePresent { template : SFCBlock | null ; styles : SFCBlock [ ] ; customBlocks : SFCCustomBlock [ ] ; } 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 ; } interface HasEscapedText { escapedText : string ; } interface HasTag { node : { body : { tag : { escapedText : string ; } ; } ; template : { text : string ; post : number ; end : number ; } ; } ; }