Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: support sanReferenceInfo in ts files #168

Merged
merged 1 commit into from
Jun 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/ast/js-ast-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ export function getRequireSpecifier (node: Node): string {

// 是否是 require 了 spec 的语句。
// 例如:对于 node = <require('san')>,isRequireSpecifier(node, 'san') === true
export function isRequireSpecifier (node: Expression, spec: string) {
return isRequire(node) && getRequireSpecifier(node) === spec
export function isRequireSpecifier (node: Expression, spec: string[]) {
return isRequire(node) && spec.includes(getRequireSpecifier(node))
}

export function isModuleExports (node: Node) {
Expand Down
26 changes: 17 additions & 9 deletions src/ast/ts-ast-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,42 @@ import type {
SourceFile, ObjectLiteralExpression
} from 'ts-morph'
import { TypeGuards, SyntaxKind } from 'ts-morph'
import debugFactory from 'debug'
import { TagName } from '../models/component-info'
import { componentID, ComponentReference } from '../models/component-reference'
import { strongParseSanSourceFileOptions } from '../compilers/renderer-options'

const debug = debugFactory('ts-ast-util')

export function getSanImportDeclaration (sourceFile: SourceFile): ImportDeclaration | undefined {
export function getSanImportDeclaration (sourceFile: SourceFile, moduleNames: string[]): ImportDeclaration | undefined {
const moduleNameSet = new Set(moduleNames)
return sourceFile.getImportDeclaration(
node => node.getModuleSpecifierValue() === 'san'
node => moduleNameSet.has(node.getModuleSpecifierValue())
)
}

/**
* import {Component as OtherName} from 'san';
* 获取到 “OtherName”
*/
export function getComponentClassIdentifier (sourceFile: SourceFile): string | undefined {
const declaration = getSanImportDeclaration(sourceFile)
export function getComponentClassIdentifier (
sourceFile: SourceFile,
sanReferenceInfo: strongParseSanSourceFileOptions['sanReferenceInfo']): string | undefined {
const declaration = getSanImportDeclaration(sourceFile, sanReferenceInfo.moduleName)
if (!declaration) return

const namedImports = declaration.getNamedImports()
const classNameSet = new Set(sanReferenceInfo.className)
for (const namedImport of namedImports) {
const name = namedImport.getName()
if (name !== 'Component') continue
if (!classNameSet.has(name)) continue

const alias = namedImport.getAliasNode()
if (alias) return alias.getText()
return 'Component'
return name
}

const defaultImport = declaration.getDefaultImport()
const text = defaultImport?.getText()
if (text && classNameSet.has(text)) {
return text
}
}

Expand Down
14 changes: 11 additions & 3 deletions src/compilers/renderer-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ export interface RenderOptions {
removeModules?: RegExp[]

sanReferenceInfo?: {
methodName?: string
moduleName?: string
className?: string
methodName?: string | string [];
moduleName?: string | string[];
className?: string | string[];
}

/**
Expand All @@ -45,3 +45,11 @@ export interface RenderOptions {
export interface parseSanSourceFileOptions {
sanReferenceInfo?: RenderOptions['sanReferenceInfo']
}

export interface strongParseSanSourceFileOptions {
sanReferenceInfo: {
methodName: string[];
moduleName: string[];
className: string[];
}
}
40 changes: 36 additions & 4 deletions src/models/san-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import type { Component } from 'san'
import type {
TypedSanSourceFile, DynamicSanSourceFile, SanSourceFile, JSSanSourceFile
} from '../models/san-source-file'
import type { parseSanSourceFileOptions, RenderOptions } from '../compilers/renderer-options'
import type {
parseSanSourceFileOptions,
RenderOptions,
strongParseSanSourceFileOptions
} from '../compilers/renderer-options'
import type { Renderer } from './renderer'
import type { CompileOptions } from '../target-js/compilers/compile-options'
import type { TargetCodeGenerator } from '../models/target-code-generator'
Expand Down Expand Up @@ -86,9 +90,17 @@ export class SanProject {
public parseSanSourceFile (input: CompileInput, options?: parseSanSourceFileOptions): SanSourceFile
public parseSanSourceFile (input: CompileInput, options?: parseSanSourceFileOptions): SanSourceFile {
if (isComponentClass(input)) return new ComponentClassParser(input, '').parse()

const formattedOptions = this.checkAndFormatParseSanSourceFileOptions(options)
if (isSanFileDescriptor(input)) {
return new SanFileParser(input.scriptContent, input.templateContent, input.filePath).parse()
return new SanFileParser(
input.scriptContent,
input.templateContent,
input.filePath,
formattedOptions
).parse()
}

const filePath = isFileDescriptor(input) ? input.filePath : input
const fileContent = isFileDescriptor(input) ? input.fileContent : undefined
if (/\.ts$/.test(filePath)) {
Expand All @@ -97,9 +109,29 @@ export class SanProject {
? this.tsProject.createSourceFile(filePath, fileContent, { overwrite: true })
: this.tsProject.addSourceFileAtPath(filePath)
!fileContent && sourceFile.refreshFromFileSystemSync()
return new TypeScriptSanParser().parse(sourceFile)
return new TypeScriptSanParser().parse(sourceFile, formattedOptions)
}
return new JavaScriptSanParser(filePath, formattedOptions).parse()
}

private checkAndFormatParseSanSourceFileOptions (options?: parseSanSourceFileOptions)
: strongParseSanSourceFileOptions {
const moduleName = options?.sanReferenceInfo?.moduleName
const methodName = options?.sanReferenceInfo?.methodName
const className = options?.sanReferenceInfo?.className
return {
sanReferenceInfo: {
moduleName: moduleName
? Array.isArray(moduleName) ? moduleName : [moduleName]
: ['san'],
className: className
? Array.isArray(className) ? className : [className]
: ['Component'],
methodName: methodName
? Array.isArray(methodName) ? methodName : [methodName]
: ['defineComponent']
}
}
return new JavaScriptSanParser(filePath, undefined, 'script', options).parse()
}

/**
Expand Down
48 changes: 17 additions & 31 deletions src/parsers/javascript-san-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {
import { JSSanSourceFile } from '../models/san-source-file'
import { componentID, ComponentReference } from '../models/component-reference'
import { readFileSync } from 'fs'
import { parseSanSourceFileOptions } from '../compilers/renderer-options'
import { strongParseSanSourceFileOptions } from '../compilers/renderer-options'

const debug = debugFactory('ts-component-parser')
const DEFAULT_LOADER_CMP = 'SanSSRDefaultLoaderComponent'
Expand Down Expand Up @@ -69,24 +69,21 @@ export class JavaScriptSanParser {
componentInfos: JSComponentInfo[] = []
entryComponentInfo?: JSComponentInfo

private sanComponentIdentifier?: string
private defineComponentIdentifier: string
private defineTemplateComponentIdentifier: string
private defaultExport?: string
private imports: Map<LocalName, [ImportSpecifier, ImportName]> = new Map()
private exports: Map<LocalName, ExportName> = new Map()
private componentIDs: Map<Node | undefined, string> = new Map()
private defaultPlaceholderComponent?: JSComponentInfo
private id = 0
private sanReferenceInfo?: parseSanSourceFileOptions['sanReferenceInfo']
private sanReferenceInfo: strongParseSanSourceFileOptions['sanReferenceInfo']

constructor (
private readonly filePath: string,
options: strongParseSanSourceFileOptions,
fileContent?: string,
sourceType: 'module' | 'script' = 'script',
options?: parseSanSourceFileOptions
sourceType: 'module' | 'script' = 'script'
) {
this.defineComponentIdentifier = 'defineComponent'
this.defineTemplateComponentIdentifier = 'defineTemplateComponent'
this.root = parse(
fileContent === undefined ? readFileSync(filePath, 'utf8') : fileContent,
Expand Down Expand Up @@ -223,19 +220,6 @@ export class JavaScriptSanParser {
parseNames () {
for (const [local, specifier, imported] of this.parseImportedNames()) {
this.imports.set(local, [specifier, imported])
if (imported === 'Component' && specifier === 'san') {
this.sanComponentIdentifier = local
}
if (imported === 'defineComponent' && specifier === 'san') {
this.defineComponentIdentifier = local
}
if (imported === 'defineTemplateComponent' && specifier === 'san') {
this.defineTemplateComponentIdentifier = local
}
}
if (this.sanReferenceInfo) {
this.sanComponentIdentifier = this.sanReferenceInfo.moduleName
this.defineComponentIdentifier = this.sanReferenceInfo.methodName || this.defineComponentIdentifier
}

for (const [local, exported] of findExportNames(this.root)) {
Expand Down Expand Up @@ -315,7 +299,7 @@ export class JavaScriptSanParser {
private getComponentType (node: ComponentDefinition): ComponentType {
if (
isCallExpression(node) &&
this.isImportedFromSanWithName(node.callee, this.defineTemplateComponentIdentifier)
this.isImportedFromSanWithName(node.callee, [this.defineTemplateComponentIdentifier])
) {
return 'template'
}
Expand All @@ -325,36 +309,38 @@ export class JavaScriptSanParser {

private isDefineComponentCall (node: Node): node is CallExpression {
return isCallExpression(node) &&
(this.isImportedFromSanWithName(node.callee, this.defineComponentIdentifier) ||
this.isImportedFromSanWithName(node.callee, this.defineTemplateComponentIdentifier))
(this.isImportedFromSanWithName(node.callee, this.sanReferenceInfo.methodName) ||
this.isImportedFromSanWithName(node.callee, [this.defineTemplateComponentIdentifier]))
}

private isCreateComponentLoaderCall (node: Node): node is CallExpression {
return isCallExpression(node) && this.isImportedFromSanWithName(node.callee, 'createComponentLoader')
return isCallExpression(node) && this.isImportedFromSanWithName(node.callee, ['createComponentLoader'])
}

private isComponentClass (node: Node): node is Class {
return isClass(node) && !!node.superClass && this.isImportedFromSanWithName(node.superClass, 'Component')
return isClass(node) && !!node.superClass &&
this.isImportedFromSanWithName(node.superClass, this.sanReferenceInfo.className)
}

private isImportedFromSanWithName (expr: Node, sanExport: string): boolean {
private isImportedFromSanWithName (expr: Node, sanExport: string[]): boolean {
if (isIdentifier(expr)) {
return this.isImportedFrom(expr.name, this.sanReferenceInfo?.moduleName || 'san', sanExport)
return this.isImportedFrom(expr.name, this.sanReferenceInfo.moduleName, sanExport)
}
if (isMemberExpression(expr)) {
return this.isImportedFromSanWithName(expr.object, 'default') && getStringValue(expr.property) === sanExport
return this.isImportedFromSanWithName(expr.object, ['default']) &&
sanExport.includes(getStringValue(expr.property))
}
if (isCallExpression(expr)) {
return isRequireSpecifier(expr, this.sanReferenceInfo?.moduleName || 'san') && sanExport === 'default'
return isRequireSpecifier(expr, this.sanReferenceInfo.moduleName) && sanExport.includes('default')
}
return false
}

private isImportedFrom (localName: string, packageSpec: string, importedName: string) {
private isImportedFrom (localName: string, packageSpec: string[], importedName: string[]) {
if (!this.imports.has(localName)) return false

const [spec, name] = this.imports.get(localName)!
return spec === packageSpec && name === importedName
return packageSpec.includes(spec) && importedName.includes(name)
}

private stringify (node: Node) {
Expand Down
6 changes: 4 additions & 2 deletions src/parsers/san-file-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@ import {
findDefaultExport
} from '../ast/js-ast-util'
import { JSSanSourceFile } from '../models/san-source-file'
import { strongParseSanSourceFileOptions } from '../compilers/renderer-options'

export class SanFileParser {
private readonly parser: JavaScriptSanParser

constructor (
public readonly scriptContent: string,
public readonly templateContent: string,
private readonly filePath: string
private readonly filePath: string,
options: strongParseSanSourceFileOptions
) {
this.parser = new JavaScriptSanParser(filePath, scriptContent, 'module')
this.parser = new JavaScriptSanParser(filePath, options, scriptContent, 'module')
}

parse (): JSSanSourceFile {
Expand Down
5 changes: 3 additions & 2 deletions src/parsers/typescript-san-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ import { TypedSanSourceFile } from '../models/san-source-file'
import { parseAndNormalizeTemplate } from './parse-template'
import { ComponentSSRType, TypedComponentInfo } from '../models/component-info'
import { componentID } from '../models/component-reference'
import type { strongParseSanSourceFileOptions } from '../compilers/renderer-options'

const debug = debugFactory('ts-component-parser')

/**
* 把包含 San 组件定义的 TypeScript 源码,通过静态分析(AST),得到组件信息。
*/
export class TypeScriptSanParser {
parse (sourceFile: SourceFile) {
const componentClassIdentifier = getComponentClassIdentifier(sourceFile)
parse (sourceFile: SourceFile, options: strongParseSanSourceFileOptions) {
const componentClassIdentifier = getComponentClassIdentifier(sourceFile, options.sanReferenceInfo)
if (!componentClassIdentifier) {
return new TypedSanSourceFile([], sourceFile)
}
Expand Down
Loading