Skip to content

Instantly share code, notes, and snippets.

@sastan
Last active March 8, 2021 15:24
Show Gist options
  • Save sastan/fd5c48e287c0703509ca8d9d605605a6 to your computer and use it in GitHub Desktop.
Save sastan/fd5c48e287c0703509ca8d9d605605a6 to your computer and use it in GitHub Desktop.
Typed Twind Plugins
import * as ts from 'typescript'
import * as fs from 'fs'
import * as path from 'path'
const { config = {} } = ts.readConfigFile(path.resolve(process.cwd(), 'tsconfig.json'), (file) =>
fs.readFileSync(file, 'utf-8'),
)
generateDocumentation([path.resolve(__dirname, 'index.ts')], config)
/** Generate documentation for all classes in a set of .ts files */
function generateDocumentation(fileNames: string[], options: ts.CompilerOptions) /*: void */ {
console.time('analyze')
// Build a program using the set of root file names in fileNames
let program = ts.createProgram({
rootNames: fileNames,
options,
})
// for (const type of program.getTypeCatalog()) {
// console.log(type.)
// }
// Get the checker, we will use it to find more about classes
const checker = program.getTypeChecker()
// // let output /*: DocEntry[]*/ = []
let classes: string[] = []
// // Visit every sourceFile in the program
for (const sourceFile of program.getSourceFiles()) {
// Walk the tree to search for classes
if (!classes.length) {
ts.forEachChild(sourceFile, visit)
}
}
console.timeEnd('analyze')
console.log(classes)
/**
* visit nodes finding exported classes
*/
function visit(node: ts.Node) {
if (classes.length) return
if (
ts.isTypeAliasDeclaration(node) &&
ts.isIdentifier(node.name) &&
node.name.escapedText == 'TwindClasses'
) {
const type = checker.getTypeAtLocation(node)
// (type.flags & ts.TypeFlags.Union) | (type.flags & ts.TypeFlags.Intersection)
const { types } = type as ts.UnionOrIntersectionType
// (type.flags & ts.TypeFlags.StringLiteral)
classes = types.map((type) => (type as ts.StringLiteralType).value)
} else {
ts.forEachChild(node, visit)
}
}
}
[
"-m-{{theme.spacing}}",
"-mb-{{theme.spacing}}",
"-ml-{{theme.spacing}}",
"-mr-{{theme.spacing}}",
"-mt-{{theme.spacing}}",
"-mx-{{theme.spacing}}",
"-my-{{theme.spacing}}",
"-space-x-{{theme.spacing}}",
"-space-y-{{theme.spacing}}",
"block",
"box-border",
"box-content",
"clear-both",
"clear-left",
"clear-none",
"clear-right",
"container",
"contents",
"flex",
"flex-col",
"flex-col-reverse",
"flex-grow-{{theme.flexGrow}}",
"flex-nowrap",
"flex-row",
"flex-row-reverse",
"flex-shrink-{{theme.flexShrink}}",
"flex-wrap",
"flex-wrap-reverse",
"flex-{{theme.flex}}",
"float-left",
"float-none",
"float-right",
"flow-root",
"grid",
"group",
"group-{{unknown}}",
"hidden",
"inline",
"inline-block",
"inline-flex",
"inline-grid",
"m-{{theme.spacing}}",
"mb-{{theme.spacing}}",
"ml-{{theme.spacing}}",
"mr-{{theme.spacing}}",
"mt-{{theme.spacing}}",
"mx-{{theme.spacing}}",
"my-{{theme.spacing}}",
"object-bottom",
"object-center",
"object-contain",
"object-cover",
"object-fill",
"object-left",
"object-left-bottom",
"object-left-top",
"object-none",
"object-right",
"object-right-bottom",
"object-right-top",
"object-scale-down",
"object-top",
"p-{{theme.spacing}}",
"pb-{{theme.spacing}}",
"pl-{{theme.spacing}}",
"pr-{{theme.spacing}}",
"pt-{{theme.spacing}}",
"px-{{theme.spacing}}",
"py-{{theme.spacing}}",
"scroll-snap-none",
"scroll-snap-x",
"scroll-snap-y",
"scroll-snap-{{theme.scrollSnap}}",
"space-x-reverse",
"space-x-{{theme.spacing}}",
"space-y-reverse",
"space-y-{{theme.spacing}}",
"table",
"table-caption",
"table-cell",
"table-column",
"table-column-group",
"table-footer-group",
"table-header-group",
"table-row",
"table-row-group"
]
// An example plugin
declare module 'twind' {
interface Theme {
scrollSnap: ThemeSection
}
interface TwindPlugins {
'scroll-snap': 'none' | 'x' | 'y' | FromTheme<'scrollSnap'>
}
interface TwindVariants {
ltr: string
}
}
type ThemeSection<Value = string> = Record<string, Value>
interface Theme {
screens: ThemeSection
flex: ThemeSection
flexGrow: ThemeSection
flexShrink: ThemeSection
spacing: ThemeSection
}
interface TwindVariants {
hover: string
focus: string
active: string
}
/**
* ```ts
* MaybeSuffix<'row', 'reverse'>
* // 'row' | 'row-reverse'
* ```
*/
type MaybeSuffix<Prefix extends string, Suffix extends string> = Join<Prefix, '' | Suffix>
type Join<Prefix extends string, Suffix extends string | never> = Suffix extends
| never
| ''
| 'DEFAULT'
? Prefix
: Suffix extends `-${infer S}`
? `-${Prefix}-${S}`
: `${Prefix}-${Suffix}`
type FromTheme<Section extends keyof Theme> = `{{theme.${Section}}}`
type Negatable<Value extends string> = Value | `-${Value}`
type Unknown = `{{unknown}}`
// TODO negate and important
interface TwindPlugins {
group: '' | Unknown
container: ''
box: 'border' | 'content'
// Display
block: ''
inline: '' | 'block' | 'flex' | 'grid'
table:
| ''
| 'caption'
| 'cell'
| 'column'
| 'column-group'
| 'footer-group'
| 'header-group'
| 'row-group'
| 'row'
flow: 'root'
grid: ''
contents: ''
hidden: ''
float: 'right' | 'left' | 'none'
clear: 'right' | 'left' | 'both' | 'none'
object: // ObjectFit
| 'contain'
| 'cover'
| 'fill'
| 'none'
| 'scale-down'
// ObjectPosition
| 'bottom'
| 'center'
| Join<'left' | 'right', '' | 'bottom' | 'top'>
| 'top'
flex:
| ''
| MaybeSuffix<'row' | 'col' | 'wrap', 'reverse'>
| 'nowrap'
| Join<'grow', FromTheme<'flexGrow'>>
| Join<'shrink', FromTheme<'flexShrink'>>
| FromTheme<'flex'>
p: FromTheme<'spacing'>
py: FromTheme<'spacing'>
px: FromTheme<'spacing'>
pt: FromTheme<'spacing'>
pr: FromTheme<'spacing'>
pb: FromTheme<'spacing'>
pl: FromTheme<'spacing'>
m: Negatable<FromTheme<'spacing'>>
my: Negatable<FromTheme<'spacing'>>
mx: Negatable<FromTheme<'spacing'>>
mt: Negatable<FromTheme<'spacing'>>
mr: Negatable<FromTheme<'spacing'>>
mb: Negatable<FromTheme<'spacing'>>
ml: Negatable<FromTheme<'spacing'>>
space: Negatable<Join<'x' | 'y', FromTheme<'spacing'>>> | Join<'x' | 'y', 'reverse'>
}
type ToString<T> = T extends string ? T : T extends number ? `${T}` : never
type JoinFromObject<T> = {
[P in keyof T]: Join<ToString<P>, ToString<T[P]>>
}[keyof T]
type TwindClasses = JoinFromObject<TwindPlugins>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment