Skip to content
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,6 @@ package-lock.json
list.txt

site/dev.js

# IDE 语法提示临时文件
vetur/
1 change: 1 addition & 0 deletions antd-tools/generator-types/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fork github.com/youzan/vant packages/generator-types
19 changes: 19 additions & 0 deletions antd-tools/generator-types/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const path = require('path');
const pkg = require('../../package.json');
const { parseAndWrite } = require('./lib/index.js');
const rootPath = path.resolve(__dirname, '../../');

try {
parseAndWrite({
version: pkg.version,
name: 'types',
path: path.resolve(rootPath, './v2-doc/src/docs'),
// default match lang
test: /en-US\.md/,
outputDir: path.resolve(rootPath, './vetur'),
tagPrefix: 'a-',
});
console.log('generator types success');
} catch (e) {
console.error('generator types error', e);
}
119 changes: 119 additions & 0 deletions antd-tools/generator-types/src/formatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/* eslint-disable no-continue */
import { Artical, Articals } from './parser';
import { formatType, removeVersion, toKebabCase } from './utils';
import { VueTag } from './type';

function getComponentName(name: string, tagPrefix: string) {
if (name) {
return tagPrefix + toKebabCase(name.split(' ')[0]);
}
return '';
}

function parserProps(tag: VueTag, line: any) {
const [name, desc, type, defaultVal] = line;
if (
type &&
(type.includes('v-slot') ||
type.includes('slot') ||
type.includes('slots') ||
type.includes('slot-scoped'))
) {
tag.slots!.push({
name: removeVersion(name),
description: desc,
});
}
tag.attributes!.push({
name: removeVersion(name),
default: defaultVal,
description: desc,
value: {
type: formatType(type || ''),
kind: 'expression',
},
});
}

export function formatter(articals: Articals, componentName: string, tagPrefix: string = '') {
if (!articals.length) {
return;
}

const tags: VueTag[] = [];
const tag: VueTag = {
name: getComponentName(componentName, tagPrefix),
slots: [],
events: [],
attributes: [],
};
tags.push(tag);

const tables = articals.filter(artical => artical.type === 'table');

tables.forEach(item => {
const { table } = item;
const prevIndex = articals.indexOf(item) - 1;
const prevArtical = articals[prevIndex];

if (!prevArtical || !prevArtical.content || !table || !table.body) {
return;
}

const tableTitle = prevArtical.content;

if (tableTitle.includes('API')) {
table.body.forEach(line => {
parserProps(tag, line);
});
return;
}

if (tableTitle.includes('events') && !tableTitle.includes(componentName)) {
table.body.forEach(line => {
const [name, desc] = line;
tag.events!.push({
name: removeVersion(name),
description: desc,
});
});
return;
}

// 额外的子组件
if (tableTitle.includes(componentName) && !tableTitle.includes('events')) {
const childTag: VueTag = {
name: getComponentName(tableTitle.replace('.', ''), tagPrefix),
slots: [],
events: [],
attributes: [],
};
table.body.forEach(line => {
parserProps(childTag, line);
});
tags.push(childTag);
return;
}
// 额外的子组件事件
if (tableTitle.includes(componentName) && tableTitle.includes('events')) {
const childTagName = getComponentName(
tableTitle.replace('.', '').replace('events', ''),
tagPrefix,
);
const childTag: VueTag | undefined = tags.find(item => item.name === childTagName.trim());
if (!childTag) {
return;
}
table.body.forEach(line => {
const [name, desc] = line;
childTag.events!.push({
name: removeVersion(name),
description: desc,
});
});
return;
}
});

return tags;
}
52 changes: 52 additions & 0 deletions antd-tools/generator-types/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import glob from 'fast-glob';
import { join, dirname, basename } from 'path';
import { mdParser } from './parser';
import { formatter } from './formatter';
import { genWebTypes } from './web-types';
import { readFileSync, outputFileSync } from 'fs-extra';
import { Options, VueTag } from './type';
import { normalizePath, getComponentName } from './utils';
import { genVeturTags, genVeturAttributes } from './vetur';

async function readMarkdown(options: Options) {
// const mds = await glob(normalizePath(`${options.path}/**/*.md`))
const mds = await glob(normalizePath(`${options.path}/**/*.md`));
return mds
.filter(md => options.test.test(md))
.map(path => {
const docPath = dirname(path);
const componentName = docPath.substring(docPath.lastIndexOf('/') + 1);
return {
componentName: getComponentName(componentName || ''),
md: readFileSync(path, 'utf-8'),
};
});
}

export async function parseAndWrite(options: Options) {
if (!options.outputDir) {
throw new Error('outputDir can not be empty.');
}

const docs = await readMarkdown(options);
const datas = docs
.map(doc => formatter(mdParser(doc.md), doc.componentName, options.tagPrefix))
.filter(item => item) as VueTag[][];
const tags: VueTag[] = [];
datas.forEach(arr => {
tags.push(...arr);
});

const webTypes = genWebTypes(tags, options);
const veturTags = genVeturTags(tags);
const veturAttributes = genVeturAttributes(tags);

outputFileSync(join(options.outputDir, 'tags.json'), JSON.stringify(veturTags, null, 2));
outputFileSync(
join(options.outputDir, 'attributes.json'),
JSON.stringify(veturAttributes, null, 2),
);
outputFileSync(join(options.outputDir, 'web-types.json'), JSON.stringify(webTypes, null, 2));
}

export default { parseAndWrite };
113 changes: 113 additions & 0 deletions antd-tools/generator-types/src/parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/* eslint-disable no-cond-assign */
const TITLE_REG = /^(#+)\s+([^\n]*)/;
const TABLE_REG = /^\|.+\r?\n\|\s*-+/;
const TD_REG = /\s*`[^`]+`\s*|([^|`]+)/g;
const TABLE_SPLIT_LINE_REG = /^\|\s*-/;

type TableContent = {
head: string[];
body: string[][];
};

export type Artical = {
type: string;
content?: string;
table?: TableContent;
level?: number;
};

export type Articals = Artical[];

function readLine(input: string) {
const end = input.indexOf('\n');

return input.substr(0, end !== -1 ? end : input.length);
}

function splitTableLine(line: string) {
line = line.replace('\\|', 'JOIN');

const items = line.split('|').map(item => item.trim().replace('JOIN', '|'));

// remove pipe character on both sides
items.pop();
items.shift();

return items;
}

function tableParse(input: string) {
let start = 0;
let isHead = true;

const end = input.length;
const table: TableContent = {
head: [],
body: [],
};

while (start < end) {
const target = input.substr(start);
const line = readLine(target);

if (!/^\|/.test(target)) {
break;
}

if (TABLE_SPLIT_LINE_REG.test(target)) {
isHead = false;
} else if (!isHead && line.includes('|')) {
const matched = line.trim().match(TD_REG);

if (matched) {
table.body.push(splitTableLine(line));
}
}

start += line.length + 1;
}

return {
table,
usedLength: start,
};
}

export function mdParser(input: string): Articals {
const artical = [];
let start = 0;
const end = input.length;
// artical.push({
// type: 'title',
// content: title,
// level: 0,
// });

while (start < end) {
const target = input.substr(start);

let match;
if ((match = TITLE_REG.exec(target))) {
artical.push({
type: 'title',
content: match[2].replace('\r', ''),
level: match[1].length,
});

start += match.index + match[0].length;
} else if ((match = TABLE_REG.exec(target))) {
const { table, usedLength } = tableParse(target.substr(match.index));
artical.push({
type: 'table',
table,
});

start += match.index + usedLength;
} else {
start += readLine(target).length + 1;
}
}

// artical[0].content = title
return artical;
}
63 changes: 63 additions & 0 deletions antd-tools/generator-types/src/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { PathLike } from 'fs';

export type VueSlot = {
name: string;
description: string;
};

export type VueEventArgument = {
name: string;
type: string;
};

export type VueEvent = {
name: string;
description?: string;
arguments?: VueEventArgument[];
};

export type VueAttribute = {
name: string;
default: string;
description: string;
value: {
kind: 'expression';
type: string;
};
};

export type VueTag = {
name: string;
slots?: VueSlot[];
events?: VueEvent[];
attributes?: VueAttribute[];
description?: string;
};

export type VeturTag = {
description?: string;
attributes: string[];
};

export type VeturTags = Record<string, VeturTag>;

export type VeturAttribute = {
type: string;
description: string;
};

export type VeturAttributes = Record<string, VeturAttribute>;

export type VeturResult = {
tags: VeturTags;
attributes: VeturAttributes;
};

export type Options = {
name: string;
path: PathLike;
test: RegExp;
version: string;
outputDir?: string;
tagPrefix?: string;
};
Loading