feat: add IDE types tips generator (#3756)
* feat: add IDE types tips generator * fix: webtypes default lang en-US * chore: rename npmpublish allow filespull/3197/head^2
parent
848d6497e6
commit
b20902412f
|
@ -69,3 +69,6 @@ package-lock.json
|
||||||
list.txt
|
list.txt
|
||||||
|
|
||||||
site/dev.js
|
site/dev.js
|
||||||
|
|
||||||
|
# IDE 语法提示临时文件
|
||||||
|
vetur/
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
fork github.com/youzan/vant packages/generator-types
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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 };
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -0,0 +1,30 @@
|
||||||
|
// myName -> my-name
|
||||||
|
export function toKebabCase(input: string): string {
|
||||||
|
return input.replace(/[A-Z]/g, (val, index) => (index === 0 ? '' : '-') + val.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
// name `v2.0.0` -> name
|
||||||
|
export function removeVersion(str: string) {
|
||||||
|
return str.replace(/`(\w|\.)+`/g, '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// *boolean* -> boolean
|
||||||
|
// _boolean_ -> boolean
|
||||||
|
export function formatType(type: string) {
|
||||||
|
return type
|
||||||
|
.replace(/(^(\*|_))|((\*|_)$)/g, '')
|
||||||
|
.replace('\\', '')
|
||||||
|
.replace('\\', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComponentName(name: string) {
|
||||||
|
let title = name
|
||||||
|
.split('-')
|
||||||
|
.map(it => it.substring(0, 1) + it.substring(1))
|
||||||
|
.join('');
|
||||||
|
return title.substring(0, 1).toUpperCase() + title.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizePath(path: string): string {
|
||||||
|
return path.replace(/\\/g, '/');
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { VueTag, VeturTags, VeturAttributes } from './type';
|
||||||
|
|
||||||
|
export function genVeturTags(tags: VueTag[]) {
|
||||||
|
const veturTags: VeturTags = {};
|
||||||
|
|
||||||
|
tags.forEach(tag => {
|
||||||
|
veturTags[tag.name] = {
|
||||||
|
attributes: tag.attributes ? tag.attributes.map(item => item.name) : [],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return veturTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function genVeturAttributes(tags: VueTag[]) {
|
||||||
|
const veturAttributes: VeturAttributes = {};
|
||||||
|
|
||||||
|
tags.forEach(tag => {
|
||||||
|
if (tag.attributes) {
|
||||||
|
tag.attributes.forEach(attr => {
|
||||||
|
veturAttributes[`${tag.name}/${attr.name}`] = {
|
||||||
|
type: attr.value.type,
|
||||||
|
description: `${attr.description}, Default: ${attr.default}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return veturAttributes;
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { VueTag, Options } from './type';
|
||||||
|
|
||||||
|
// create web-types.json to provide autocomplete in JetBrains IDEs
|
||||||
|
export function genWebTypes(tags: VueTag[], options: Options) {
|
||||||
|
return {
|
||||||
|
$schema: 'https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json',
|
||||||
|
framework: 'vue',
|
||||||
|
name: options.name,
|
||||||
|
version: options.version,
|
||||||
|
contributions: {
|
||||||
|
html: {
|
||||||
|
tags,
|
||||||
|
attributes: [],
|
||||||
|
'types-syntax': 'typescript',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"outDir": "./lib",
|
||||||
|
"module": "commonjs",
|
||||||
|
"strict": true,
|
||||||
|
"declaration": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": ["esnext"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"]
|
||||||
|
}
|
12
package.json
12
package.json
|
@ -24,7 +24,8 @@
|
||||||
"dist",
|
"dist",
|
||||||
"lib",
|
"lib",
|
||||||
"es",
|
"es",
|
||||||
"scripts"
|
"scripts",
|
||||||
|
"vetur"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "webpack-dev-server",
|
"dev": "webpack-dev-server",
|
||||||
|
@ -32,6 +33,7 @@
|
||||||
"test": "cross-env NODE_ENV=test WORKFLOW=true jest --config .jest.js",
|
"test": "cross-env NODE_ENV=test WORKFLOW=true jest --config .jest.js",
|
||||||
"test:dev": "cross-env NODE_ENV=test jest --config .jest.js",
|
"test:dev": "cross-env NODE_ENV=test jest --config .jest.js",
|
||||||
"compile": "node antd-tools/cli/run.js compile",
|
"compile": "node antd-tools/cli/run.js compile",
|
||||||
|
"generator-webtypes": "tsc -p antd-tools/generator-types/tsconfig.json && node antd-tools/generator-types/index.js",
|
||||||
"pub": "node antd-tools/cli/run.js pub",
|
"pub": "node antd-tools/cli/run.js pub",
|
||||||
"pub-with-ci": "node antd-tools/cli/run.js pub-with-ci",
|
"pub-with-ci": "node antd-tools/cli/run.js pub-with-ci",
|
||||||
"prepublish": "node antd-tools/cli/run.js guard",
|
"prepublish": "node antd-tools/cli/run.js guard",
|
||||||
|
@ -82,6 +84,7 @@
|
||||||
"@commitlint/cli": "^8.0.0",
|
"@commitlint/cli": "^8.0.0",
|
||||||
"@commitlint/config-conventional": "^8.0.0",
|
"@commitlint/config-conventional": "^8.0.0",
|
||||||
"@octokit/rest": "^16.0.0",
|
"@octokit/rest": "^16.0.0",
|
||||||
|
"@types/fs-extra": "^9.0.8",
|
||||||
"@types/lodash-es": "^4.17.3",
|
"@types/lodash-es": "^4.17.3",
|
||||||
"@types/raf": "^3.4.0",
|
"@types/raf": "^3.4.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.1.0",
|
"@typescript-eslint/eslint-plugin": "^4.1.0",
|
||||||
|
@ -229,5 +232,10 @@
|
||||||
"es/**/style/*",
|
"es/**/style/*",
|
||||||
"lib/**/style/*",
|
"lib/**/style/*",
|
||||||
"*.less"
|
"*.less"
|
||||||
]
|
],
|
||||||
|
"vetur": {
|
||||||
|
"tags": "vetur/tags.json",
|
||||||
|
"attributes": "vetur/attributes.json"
|
||||||
|
},
|
||||||
|
"web-types": "vetur/web-types.json"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue