Skip to content

Commit

Permalink
deploy time
Browse files Browse the repository at this point in the history
  • Loading branch information
KonnorRogers committed Oct 14, 2023
1 parent a134608 commit b29c87c
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 13 deletions.
1 change: 1 addition & 0 deletions .github/workflows/deploy-site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ jobs:
- name: Build website
run: |
pnpm run setup
pnpm run build
pnpm run build:docs
pnpm run build:api
Expand Down
26 changes: 13 additions & 13 deletions custom-elements-manifest.config.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
// @ts-check
// import { expandTypesPlugin } from './expand-types.js'

// import { defaultCompilerOptions } from './compilerOptions.js';
// import { myPlugin } from './my-plugin.js';
// import { expandTypes } from './expandTypes'
// import ts from "typescript"

// let typeChecker = null
const globs = ['exports/**/*.{d.ts,js}', 'internal/**/*.{d.ts,js}', 'types/**/*.d.ts']

export default {
/** Globs to analyze */
globs: ['./exports/**/*.js', './internal/**/*.js', './types/**/*.d.ts'],
globs,
/** Globs to exclude */
exclude: ['./node_modules', './docs'],
exclude: ['node_modules', 'docs'],
/** Directory to output CEM to */
outdir: '.',
/** Run in dev mode, provides extra logging */
Expand All @@ -30,15 +26,19 @@ export default {
fast: false,
/** Enable special handling for stencil */
stencil: false,
// overrideModuleCreation: ({globs}) => {
// overrideModuleCreation: ({ts, globs}) => {
// const program = ts.createProgram(globs, {target: ts.ScriptTarget.ESNext, module: ts.ModuleKind.ESNext, allowJs: true, checkJs: true});
// typeChecker = program.getTypeChecker();
//
// return program.getSourceFiles().filter(sf => globs.find(glob => sf.fileName.includes(glob)));
// // If we dont do this, everything blows up.
// program.getTypeChecker()
//
// return program.getSourceFiles().filter(sf => globs.find(glob => {
// return sf.fileName.includes(glob)
// }))
// },
/** Provide custom plugins */
// /** Provide custom plugins */
// plugins: [
// /** You can now pass the typeChecker to your plugins */
// expandTypes(typeChecker)
// expandTypesPlugin({ globs })
// ],
}
209 changes: 209 additions & 0 deletions expand-types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// @ts-check
import typeScript from "typescript"
// import * as fs from "fs"

import { globSync } from "glob"


/**
* @typedef {Array<string | TypeArray>} TypeArray
*/

/** @typedef {{
name?: string;
fileName?: string;
documentation?: string;
type?: string;
constructors?: DocEntry[];
parameters?: DocEntry[];
returnType?: string;
}} DocEntry */


class ExpandTypes {
/**
* @param {Object} options
* @param {string | string[]} options.globs
* @param {import("typescript") | null | undefined} [options.ts]
* @param {import("typescript").Program | null | undefined} [options.program]
*/
constructor ({ globs, ts = null, program = null}) {
this.name = "expand-types-plugin"
this.ts = ts || typeScript
this.program = program

this.globs = globSync(globs)

/**
* @type {import("typescript").TypeChecker | null | undefined}
*/
this.typeChecker = this.program?.getTypeChecker()
this.sourceFiles = new Map()
}

createProgram = () => {
if (!this.ts) throw Error("no TS instance Found")

const program = this.ts.createProgram(this.globs, {target: this.ts.ScriptTarget.ESNext, module: this.ts.ModuleKind.ESNext, allowJs: true, checkJs: true});
return program
}

collectPhase = ({ ts, node, context }) => {
if (this.checked) return

if (!this.ts) {
this.ts = ts
}

if (!this.program) {
this.program = this.createProgram()
}

if (!this.program) {
throw Error("Could not find program")
}

if (!this.ts) {
throw Error("Could not find TS Instance")
}

const program = this.program

if (!this.typeChecker) {
this.typeChecker = program.getTypeChecker()
}

this.checked = true

const sourceFilesMap = this.sourceFiles
const sourceFiles = this.program.getSourceFiles()
.filter(sf => this.globs.find(glob => {
return sf.fileName.includes(glob)
}))

for (const sourceFile of sourceFiles) {
if (!sourceFile) return

const types = new Map()

// Walk the tree to search for classes
this.ts.forEachChild(sourceFile, (node) => this.__processNode(node, types))

sourceFilesMap.set(sourceFile.fileName, types)
}

// console.log([...[sourceFilesMap.entries()].values()])
}

/**
* @param {import("typescript").Node} node
* @param {Map<string, string>} types
*/
__processNode = (node, types) => {
if (!this.typeChecker) return
if (!this.ts) return

const ts = this.ts

if (ts.isTypeAliasDeclaration(node)) {
const type = this.typeChecker.getTypeAtLocation(node.name);

const originalProperty = this.typeChecker.typeToString(type)
const processedStr = this.__processProperty(type, node)
types.set(originalProperty, processedStr)
}
ts.forEachChild(node, (node) => this.__processNode(node, types))
}

/**
* Typescript can help us to spot types from outside of our local source files
* which we don't want to process like literals string (think of trim(), length etc) or entire classes like Date.
* @param {import("typescript").Symbol} symbol
*/
__isTypeLocal = (symbol) => {
if (!this.program) return false

const sourceFile = symbol?.valueDeclaration?.getSourceFile();

const isStandardLibrary = !!sourceFile && this.program.isSourceFileDefaultLibrary(sourceFile)
const isExternal = !!sourceFile && this.program.isSourceFileFromExternalLibrary(sourceFile);
const hasDeclaration = !!symbol?.declarations?.[0];

return !(isStandardLibrary || isExternal) && hasDeclaration;
}

/**
* @param {import("typescript").Type} type
* @param {import("typescript").Node} node
* @param {number} [level=0]
* @return {string}
*/
__processProperty = (type, node, level = 0) => {
if (!this.ts) return ""
if (!this.typeChecker) return ""

const ts = this.ts
const checker = this.typeChecker

if (!ts.isTypeAliasDeclaration(node)) return ""

const group = []

if (ts.isIntersectionTypeNode(node.type) || ts.isUnionTypeNode(node.type)) {
const type = checker.getTypeAtLocation(node.type)

if (type.isLiteral()) {
return checker.typeToString(type)
}

let separator = ""

if (type.isUnion() || type.isIntersection()) {
if (type.isUnion()) separator = "|"
if (type.isIntersection()) separator = "&"

for (const t of type.types) {
group.push(checker.typeToString(t))
}
}

return group.filter(Boolean).join(` ${separator} `)
}

// return checker.typeToString(type)

// Flattens objects
const obj = {}

for (const property of type.getProperties()) {
const propertyType = checker.getTypeOfSymbolAtLocation(property, node);
const propertySymbol = propertyType.getSymbol();
const propertyTypeName = checker.typeToString(propertyType);

/**
* If it's a local type belonging to our sources we are interested in
* further analysis, so we process all properties again like we did for the current given property.
*/
if(propertySymbol && this.__isTypeLocal(propertySymbol)) {
const properties = this.__processProperty(propertyType, node, level + 1)

obj[property.name] = properties
}else {
group.push(property.name)
obj[property.name] = propertyTypeName
}
}

return JSON.stringify(obj, null, 2)
}
}

/**
* @param {Object} options
* @param {string[]} options.globs
* @param {import("typescript") | null | undefined} [options.ts]
* @param {import("typescript").Program | null | undefined} [options.program]
*/
export function expandTypesPlugin ({ globs, ts = null, program = null}) {
return new ExpandTypes({ globs, ts, program })
}

0 comments on commit b29c87c

Please sign in to comment.