Skip to content

Commit

Permalink
Fix typings (#439)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi authored Feb 7, 2025
1 parent 8f95816 commit ae1d269
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 41 deletions.
2 changes: 1 addition & 1 deletion packages/cheetah-grid/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cheetah-grid",
"version": "1.16.0",
"version": "1.16.2",
"description": "Cheetah Grid is a high performance grid engine that works on canvas",
"keywords": [
"spreadsheet",
Expand Down
13 changes: 8 additions & 5 deletions packages/cheetah-grid/src/js/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,6 @@ export { getInternal as _getInternal } from "./get-internal";
* @namespace cheetahGrid
*/
export {
/**
* Types
* @namespace cheetahGrid.TYPES
*/
TYPES,
core,
tools,
// impl Grids
Expand All @@ -52,6 +47,14 @@ export {
register,
};

export type {
/**
* Types
* @namespace cheetahGrid.TYPES
*/
TYPES,
};

/** @private */
function getIcons(): { [key: string]: TYPES.IconDefine } {
return icons.get();
Expand Down
3 changes: 2 additions & 1 deletion packages/cheetah-grid/src/js/ts-types/grid-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import type {
StylePropertyFunctionArg,
TextOverflow,
} from "./define";
import type { ColumnDefine } from "../ListGrid";
import type { ColumnDefine, HeadersDefine } from "../list-grid/layout-map/api";
import type { RecordBoolean } from "./column";
import type { RequiredThemeDefine } from "./plugin";
import type { SimpleColumnIconOption } from "../ts-types-internal/data";
Expand Down Expand Up @@ -162,6 +162,7 @@ export interface ListGridAPI<T> extends DrawGridAPI {
dataSource: DataSourceAPI<T>;
theme: RequiredThemeDefine | null;
allowRangePaste: boolean;
header: HeadersDefine<T>;
headerRowHeight: number[] | number;
sortState: SortState | null;
headerValues: HeaderValues;
Expand Down
2 changes: 2 additions & 0 deletions packages/cheetah-grid/src/js/ts-types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ export * from "./plugin";
export * from "./define";
export * from "./data";
export * from "./grid-engine";

export type { ColumnDefine, HeadersDefine } from "../list-grid/layout-map/api";
2 changes: 1 addition & 1 deletion packages/vue-cheetah-grid/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vue-cheetah-grid",
"version": "1.16.1",
"version": "1.16.2",
"description": "Cheetah Grid for Vue.js",
"main": "lib/index.js",
"unpkg": "dist/vueCheetahGrid.js",
Expand Down
30 changes: 24 additions & 6 deletions packages/vue-cheetah-grid/scripts/lib/merge.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = {
const merge = require('deepmerge')
const c = merge.all(components)
c.props = mergeProps(components)
c.methods = mergeMethods(components)
return c
}
}
Expand All @@ -27,15 +28,15 @@ const PROPS = [

function mergeProps (components) {
const props = components.flatMap(c => c.props)
const mergeProps = []
const mergedProps = []
for (const prop of props) {
const idx = mergeProps.findIndex((t) => t.name === prop.name)
const idx = mergedProps.findIndex((t) => t.name === prop.name)
if (idx >= 0) {
mergeProps.splice(idx, 1)
mergedProps.splice(idx, 1)
}
mergeProps.push(prop)
mergedProps.push(prop)
}
mergeProps.sort((a, b) => {
mergedProps.sort((a, b) => {
const ai = PROPS.indexOf(a.name)
const bi = PROPS.indexOf(b.name)
if (ai >= 0 && bi >= 0) {
Expand All @@ -50,7 +51,24 @@ function mergeProps (components) {
return compare(a.name, b.name)
})

return mergeProps
return mergedProps
}

function mergeMethods (components) {
const methods = components.flatMap(c => c.methods)
const mergedMethods = []
for (const method of methods) {
const idx = mergedMethods.findIndex((t) => t.name === method.name)
if (idx >= 0) {
mergedMethods.splice(idx, 1)
}
mergedMethods.push(method)
}
mergedMethods.sort((a, b) => {
return compare(a.name, b.name)
})

return mergedMethods
}

function compare (a, b) {
Expand Down
60 changes: 54 additions & 6 deletions packages/vue-cheetah-grid/scripts/lib/metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,42 @@ const path = require('path')
const vuedoc = require('@vuedoc/md')
const merge = require('./merge')

/**
* @typedef {object} Keyword
* @property {string} name
* @property {string} description
*/
/**
* @typedef {object} ComponentPropMetadata
* @property {string} name
* @property {string} description
* @property {{ name: string, description: string }[]} keywords
* @property {Keyword[]} keywords
* @property {string | { type: string, required: boolean, default: any }} value
*/
/**
* @typedef {object} ComponentMethodMetadata
* @property {string} name
* @property {string} description
* @property {Keyword[]} keywords
* @property {{ type: 'FunctionExpression' }} value
* @property {'public'|undefined} visibility
* @property {string[]} args
* @property {unknown} return
*/
/**
* @typedef {object} ComponentMetadata
* @property {string} name
* @property {string} description
* @property {{ name: string, description: string }[]} keywords
* @property {Keyword[]} keywords
* @property {ComponentPropMetadata[]} props
* @property {ComponentMethodMetadata[]} methods
*/

module.exports = {
getAllVueComponentMetadata,
getPropType,
isRequiredProp
isRequiredProp,
getMethodSignature
}

/**
Expand Down Expand Up @@ -55,17 +72,48 @@ async function getAllVueComponentMetadata () {
/**
* @param {ComponentPropMetadata} prop
*/

function getPropType (prop) {
const customTypeKeyword = prop.keywords.find(({ name, description }) => name === 'type' && description)
const customType = customTypeKeyword && customTypeKeyword.description.replace(/\{(.+?)\}/, '$1')
const customType = customTypeKeyword && getKeywordType(customTypeKeyword)
const type = customType || prop.value.type || prop.value || 'any'
return parseType(type)
}

/**
* @param {ComponentPropMetadata} prop
* @param {ComponentMethodMetadata} method
*/
function getMethodSignature (method) {
const paramKeywords = {}
for (const keyword of method.keywords.filter(({ name, description }) => name === 'param')) {
const typeClosing = keyword.description.indexOf('}')
if (typeClosing < 0) continue
const paramNameAndDesc = keyword.description.slice(typeClosing + 1).trim()
const props = /^\[?(\p{ID_Start}\p{ID_Continue}*(?:\.\p{ID_Start}\p{ID_Continue}*)*)/u.exec(paramNameAndDesc)[1].split('.')
if (props.length === 1) {
paramKeywords[props[0]] = getKeywordType(keyword)
}
}

const argsSignature = method.args
.map(arg => {
const keyword = paramKeywords[arg]
return (keyword && getKeywordType(keyword)) || 'any'
})
.join(', ')
const returnKeyword = method.keywords.find(({ name, description }) => name === 'return' && description)
const returnType = (returnKeyword && getKeywordType(returnKeyword)) || 'any'
return `(${argsSignature}) => ${returnType}`
}
/**
* @param {Keyword} keyword
*/
function getKeywordType (keyword) {
return /^\s*\{(.+)\}/u.exec(keyword.description)?.[1]
}

/**
* @param {ComponentPropMetadata} prop
*/
function isRequiredProp (prop) {
return typeof prop.value !== 'string' && Boolean(prop.value.required)
}
Expand Down
44 changes: 23 additions & 21 deletions packages/vue-cheetah-grid/scripts/vue3-types.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const fs = require('fs')
const path = require('path')
const { getAllVueComponentMetadata, getPropType, isRequiredProp } = require('./lib/metadata')
const { getAllVueComponentMetadata, getPropType, isRequiredProp, getMethodSignature } = require('./lib/metadata')
const cheetahGrid = require('cheetah-grid')
const { EVENT_TYPE } = cheetahGrid.ListGrid
const vue3Emits = Object.keys(EVENT_TYPE)
Expand All @@ -25,21 +25,34 @@ async function main () {
${camelCase(prop.name)}${isRequiredProp(prop) ? '' : '?'}: ${normalizePropType(prop)};
`.trim()
})
const methods = component.methods
.filter(method => method.visibility === 'public')
.map(method => {
return `
/** ${method.description} */
${method.name}: ${getMethodSignature(method)};
`.trim()
})
const emits = Object.keys(vue3Emits).map(emitName => {
return `
on${pascalCase(emitName)}?: Function;
`.trim()
})
componentTypes.push(`
/** ${component.description} */
export type ${componentName} = GlobalComponentConstructor<{
${indent([...props, ...emits].join('\n'), 2)}
}>;
export const ${componentName}: ComponentConstructor<
{
${indent([...props, ...emits].join('\n'), 4)}
},
{
${indent(methods.join('\n'), 4)}
}
>;
`.trim())
components.push(`
/** ${component.description} */
${componentName}: ${componentName};
"${kebabCase(componentName)}": ${componentName};
${componentName}: typeof ${componentName};
"${kebabCase(componentName)}": typeof ${componentName};
`.trim())
}

Expand All @@ -49,25 +62,14 @@ ${componentName}: ${componentName};
fs.mkdirSync(typeDir)
}
fs.writeFileSync(typePath, `${`
import { VNodeProps, AllowedComponentProps, ComponentCustomProps } from "@vue/runtime-core";
import { PublicProps } from "vue";
export * as cheetahGrid from 'cheetah-grid'
// type VueInstance = ComponentPublicInstance
/* @see https://unpkg.com/browse/[email protected]/dist/types/ts-helpers.d.ts */
// https://github.com/vuejs/vue-next/blob/d84d5ecdbdf709570122175d6565bb61fae877f2/packages/runtime-core/src/apiDefineComponent.ts#L29-L31
// TODO: This can be imported from vue directly once this PR gets merged: https://github.com/vuejs/vue-next/pull/2403
type PublicProps = VNodeProps & AllowedComponentProps & ComponentCustomProps;
// Can't use \`DefineComponent\` because of the false prop inferring behavior, it doesn't pick up the required types when an interface is passed
// This PR will probably solve the problem as it moves the prop inferring behavior to \`defineComponent\` function: https://github.com/vuejs/vue-next/pull/4465
// GlobalComponentConstructor helper is kind of like the ComponentConstructor type helper, but simpler and keeps the Volar errors simpler,
// and also similar to the usage in official Vue packages: https://github.com/vuejs/vue-next/blob/d84d5ecdbdf709570122175d6565bb61fae877f2/packages/runtime-core/src/components/BaseTransition.ts#L258-L264 or https://github.com/vuejs/vue-router-next/blob/5dd5f47515186ce34efb9118dda5aad0bb773439/src/RouterView.ts#L160-L172 etc.
// TODO: This can be replaced with \`DefineComponent\` once this PR gets merged: https://github.com/vuejs/vue-next/pull/4465
type GlobalComponentConstructor<Props = {}, Slots = {}> = {
type ComponentConstructor<Props = {}, Methods = {}, Slots = {}> = {
new (): {
$props: PublicProps & Props
$slots: Slots
}
} & Methods
}
${componentTypes.join('\n')}
Expand Down

0 comments on commit ae1d269

Please sign in to comment.