prettier#format TypeScript Examples
The following examples show how to use
prettier#format.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: utils.ts From amplication with Apache License 2.0 | 6 votes |
export function printTypescript(ast: ASTNode): string {
return format(print(ast).code, { parser: "typescript" });
}
Example #2
Source File: remove-nodes-from-original-code.spec.ts From prettier-plugin-sort-imports with Apache License 2.0 | 6 votes |
test('it should remove nodes from the original code', () => {
const importNodes = getImportNodes(code);
const sortedNodes = getSortedNodes(importNodes, {
importOrder: [],
importOrderCaseInsensitive: false,
importOrderSeparation: false,
importOrderGroupNamespaceSpecifiers: false,
importOrderSortSpecifiers: false,
});
const allCommentsFromImports = getAllCommentsFromNodes(sortedNodes);
const commentAndImportsToRemoveFromCode = [
...sortedNodes,
...allCommentsFromImports,
];
const codeWithoutImportDeclarations = removeNodesFromOriginalCode(
code,
commentAndImportsToRemoveFromCode,
);
const result = format(codeWithoutImportDeclarations, { parser: 'babel' });
expect(result).toEqual('');
});
Example #3
Source File: utils.ts From prettier-plugin-jsdoc with MIT License | 6 votes |
function formatType(type: string, options?: Options): string {
try {
const TYPE_START = "type name = ";
let pretty = type;
// Rest parameter types start with "...". This is supported by TS and JSDoc
// but it's implemented in a weird way in TS. TS will only acknowledge the
// "..." if the function parameter is a rest parameter. In this case, TS
// will interpret `...T` as `T[]`. But we can't just convert "..." to arrays
// because of @callback types. In @callback types `...T` and `T[]` are not
// equivalent, so we have to support "..." as is.
//
// This formatting itself is rather simple. If `...T` is detected, it will
// be replaced with `T[]` and formatted. At the end, the outer array will
// be removed and "..." will be added again.
//
// As a consequence, union types will get an additional pair of parentheses
// (e.g. `...A|B` -> `...(A|B)`). This is technically unnecessary but it
// makes the operator precedence very clear.
//
// https://www.typescriptlang.org/docs/handbook/functions.html#rest-parameters
let rest = false;
if (pretty.startsWith("...")) {
rest = true;
pretty = `(${pretty.slice(3)})[]`;
}
pretty = format(`${TYPE_START}${pretty}`, {
...options,
parser: "typescript",
plugins: [],
filepath: "file.ts",
});
pretty = pretty.slice(TYPE_START.length);
pretty = pretty.replace(/^\s*/g, "");
pretty = pretty.replace(/[;\n]*$/g, "");
pretty = pretty.trim();
if (rest) {
pretty = "..." + pretty.replace(/\[\s*\]$/, "");
}
return pretty;
} catch (error) {
// console.log("jsdoc-parser", error);
return type;
}
}
Example #4
Source File: utils.ts From prettier-plugin-jsdoc with MIT License | 6 votes |
function formatCode(
result: string,
beginningSpace: string,
options: AllOptions,
): string {
const { printWidth, jsdocKeepUnParseAbleExampleIndent } = options;
if (
result
.split("\n")
.slice(1)
.every((v) => !v.trim() || v.startsWith(beginningSpace))
) {
result = result.replace(
new RegExp(`\n${beginningSpace.replace(/[\t]/g, "[\\t]")}`, "g"),
"\n",
);
}
try {
let formattedExample = "";
const examplePrintWith = printWidth - 4;
// If example is a json
if (result.trim().startsWith("{")) {
formattedExample = format(result || "", {
...options,
parser: "json",
printWidth: examplePrintWith,
});
} else {
formattedExample = format(result || "", {
...options,
printWidth: examplePrintWith,
});
}
result = formattedExample.replace(/(^|\n)/g, `\n${beginningSpace}`); // Add spaces to start of lines
} catch (err) {
result = `\n${result
.split("\n")
.map(
(l) =>
`${beginningSpace}${jsdocKeepUnParseAbleExampleIndent ? l : l.trim()
}`,
)
.join("\n")}\n`;
}
return result;
}
Example #5
Source File: get-code-from-ast.spec.ts From prettier-plugin-sort-imports with Apache License 2.0 | 6 votes |
test('it sorts imports correctly', () => {
const code = `// first comment
// second comment
import z from 'z';
import c from 'c';
import g from 'g';
import t from 't';
import k from 'k';
import a from 'a';
`;
const importNodes = getImportNodes(code);
const sortedNodes = getSortedNodes(importNodes, {
importOrder: [],
importOrderCaseInsensitive: false,
importOrderSeparation: false,
importOrderGroupNamespaceSpecifiers: false,
importOrderSortSpecifiers: false,
});
const formatted = getCodeFromAst(sortedNodes, code, null);
expect(format(formatted, { parser: 'babel' })).toEqual(
`// first comment
// second comment
import a from "a";
import c from "c";
import g from "g";
import k from "k";
import t from "t";
import z from "z";
`,
);
});
Example #6
Source File: remark.test.ts From joplin-blog with MIT License | 6 votes |
it('测试任务列表序列化', () => {
const text = `
# test
- [x] task 1
- [ ] task 2
`
const tree = md.parse(text)
const res = md.stringify(tree)
console.log(text)
console.log(res)
console.log(format(res, { parser: 'markdown', tabWidth: 2 } as Options))
})
Example #7
Source File: generate-configs.ts From graphql-eslint with MIT License | 6 votes |
writeFormattedFile: WriteFile = (filePath, code): void => {
const isJson = filePath.endsWith('.json');
const formattedCode = isJson
? format(JSON.stringify(code), {
parser: 'json',
printWidth: 80,
})
: [
'/*',
' * ? IMPORTANT! Do not manually modify this file. Run: `yarn generate-configs`',
' */',
BR,
format(code, {
...prettierOptions,
parser: 'typescript',
}),
].join('\n');
writeFileSync(join(SRC_PATH, filePath), formattedCode);
// eslint-disable-next-line no-console
console.log(`✅ ${chalk.green(filePath)} file generated`);
}
Example #8
Source File: solita.ts From solita with Apache License 2.0 | 6 votes |
private renderImportIndex(modules: string[], label: string) {
let code = modules.map((x) => `export * from './${x}';`).join('\n')
if (this.formatCode) {
try {
code = format(code, this.formatOpts)
} catch (err) {
logError(`Failed to format ${label} imports`)
logError(err)
}
}
return code
}
Example #9
Source File: JoplinNoteParser.ts From joplin-blog with MIT License | 6 votes |
/**
* 转换笔记中的 joplin 内部链接
* @param content
* @param map
*/
convertResource(content: string, map: Map<string, string>) {
const tree = unistUtilMap(this.processor.parse(content), (node) => {
if (node.type !== 'link' && node.type !== 'image') {
return node
}
const link = node as Link
if (!link.url.startsWith(':/')) {
return link
}
return {
...node,
url: map.get(link.url.slice(2)),
} as Link
})
const options: Options = { parser: 'markdown', tabWidth: 2 }
return format(this.processor.stringify(tree), options)
}
Example #10
Source File: solita.ts From solita with Apache License 2.0 | 5 votes |
// -----------------
// Main Index File
// -----------------
private async writeMainIndex(reexports: string[]) {
assert(this.paths != null, 'should have set paths')
const programAddress = this.idl.metadata.address
const reexportCode = this.renderImportIndex(reexports.sort(), 'main')
const imports = `import { PublicKey } from '${SOLANA_WEB3_PACKAGE}'`
const programIdConsts = `
/**
* Program address
*
* @category constants
* @category generated
*/
export const PROGRAM_ADDRESS = '${programAddress}'
/**
* Program public key
*
* @category constants
* @category generated
*/
export const PROGRAM_ID = new PublicKey(PROGRAM_ADDRESS)
`
let code = `
${imports}
${reexportCode}
${programIdConsts}
`.trim()
if (this.formatCode) {
try {
code = format(code, this.formatOpts)
} catch (err) {
logError(`Failed to format mainIndex`)
logError(err)
}
}
await fs.writeFile(path.join(this.paths.root, `index.ts`), code, 'utf8')
}
Example #11
Source File: __helpers__.ts From typecheck.macro with MIT License | 5 votes |
export function snapshotFunction(t: ExecutionContext, f: Function) {
t.snapshot(format(f.toString(), { parser: "babel" }));
}
Example #12
Source File: JoplinNoteHandler.ts From joplin-utils with MIT License | 5 votes |
static format(node: Node) {
return format(this.md.stringify(node), {
parser: 'markdown',
tabWidth: 2,
} as Options)
}
Example #13
Source File: JoplinNoteHandler.ts From joplin-blog with MIT License | 5 votes |
static format(node: Node) {
return format(this.md.stringify(node), {
parser: 'markdown',
tabWidth: 2,
} as Options)
}
Example #14
Source File: fs.ts From nx-dotnet with MIT License | 5 votes |
export function writeJson(path: string, object: any) {
const contents = format(JSON.stringify(object, null, 2), {
parser: 'json',
});
return writeFileSync(path, contents);
}
Example #15
Source File: TestUtils.tsx From kfp-tekton-backend with Apache License 2.0 | 5 votes |
export function formatHTML(html: string): string {
return format(html, { parser: 'html' });
}
Example #16
Source File: index.ts From swagger-typescript with MIT License | 5 votes |
function formatFile(filePath: string, prettierOptions: any) {
const code = readFileSync(filePath).toString();
writeFileSync(filePath, format(code, prettierOptions));
}
Example #17
Source File: solita.ts From solita with Apache License 2.0 | 4 votes |
// -----------------
// Render
// -----------------
renderCode() {
assert(this.paths != null, 'should have set paths')
const programId = this.idl.metadata.address
const fixableTypes: Set<string> = new Set()
const accountFiles = this.accountFilesByType()
const customFiles = this.customFilesByType()
function forceFixable(ty: IdlType) {
if (isIdlTypeDefined(ty) && fixableTypes.has(ty.defined)) {
return true
}
return false
}
// NOTE: we render types first in order to know which ones are 'fixable' by
// the time we render accounts and instructions
// However since types may depend on other types we obtain this info in 2 passes.
// -----------------
// Types
// -----------------
const types: Record<string, string> = {}
logDebug('Rendering %d types', this.idl.types?.length ?? 0)
if (this.idl.types != null) {
for (const ty of this.idl.types) {
// Here we detect if the type itself is fixable solely based on its
// primitive field types
let isFixable = determineTypeIsFixable(
ty,
this.paths.typesDir,
accountFiles,
customFiles
)
if (isFixable) {
fixableTypes.add(ty.name)
}
}
for (const ty of this.idl.types) {
logDebug(`Rendering type ${ty.name}`)
logTrace('kind: %s', ty.type.kind)
if (isIdlDefinedType(ty.type)) {
logTrace('fields: %O', ty.type.fields)
} else {
if (isIdlTypeEnum(ty.type)) {
logTrace('variants: %O', ty.type.variants)
}
}
let { code, isFixable } = renderType(
ty,
this.paths!.typesDir,
accountFiles,
customFiles,
this.typeAliases,
forceFixable
)
// If the type by itself does not need to be fixable, here we detect if
// it needs to be fixable due to including a fixable type
if (isFixable) {
fixableTypes.add(ty.name)
}
if (this.prependGeneratedWarning) {
code = prependGeneratedWarning(code)
}
if (this.formatCode) {
try {
code = format(code, this.formatOpts)
} catch (err) {
logError(`Failed to format ${ty.name} instruction`)
logError(err)
}
}
types[ty.name] = code
}
}
// -----------------
// Instructions
// -----------------
const instructions: Record<string, string> = {}
for (const ix of this.idl.instructions) {
logDebug(`Rendering instruction ${ix.name}`)
logTrace('args: %O', ix.args)
logTrace('accounts: %O', ix.accounts)
let code = renderInstruction(
ix,
this.paths.instructionsDir,
programId,
accountFiles,
customFiles,
this.typeAliases,
forceFixable
)
if (this.prependGeneratedWarning) {
code = prependGeneratedWarning(code)
}
if (this.formatCode) {
try {
code = format(code, this.formatOpts)
} catch (err) {
logError(`Failed to format ${ix.name} instruction`)
logError(err)
}
}
instructions[ix.name] = code
}
// -----------------
// Accounts
// -----------------
const accounts: Record<string, string> = {}
for (const account of this.idl.accounts ?? []) {
logDebug(`Rendering account ${account.name}`)
logTrace('type: %O', account.type)
let code = renderAccount(
account,
this.paths.accountsDir,
accountFiles,
customFiles,
this.typeAliases,
this.serializers,
forceFixable,
this.resolveFieldType,
this.accountsHaveImplicitDiscriminator
)
if (this.prependGeneratedWarning) {
code = prependGeneratedWarning(code)
}
if (this.formatCode) {
try {
code = format(code, this.formatOpts)
} catch (err) {
logError(`Failed to format ${account.name} account`)
logError(err)
}
}
accounts[account.name] = code
}
// -----------------
// Errors
// -----------------
logDebug('Rendering %d errors', this.idl.errors?.length ?? 0)
let errors = renderErrors(this.idl.errors ?? [])
if (errors != null && this.prependGeneratedWarning) {
errors = prependGeneratedWarning(errors)
}
if (errors != null && this.formatCode) {
try {
errors = format(errors, this.formatOpts)
} catch (err) {
logError(`Failed to format errors`)
logError(err)
}
}
return { instructions, accounts, types, errors }
}
Example #18
Source File: index.tsx From jetlinks-ui-antd with MIT License | 4 votes |
PropertiesDefin: React.FC<Props> = props => {
const version = localStorage.getItem('system-version');
const initState: State = {
dataType: props.data.valueType?.type || '',
aType: props.data.valueType?.elementType?.type || '',
data: props.data,
enumData: props.data.valueType?.elements || [{ text: '', value: '', id: 0 }],
arrayEnumData: props.data.valueType?.elementType?.elements || [{ text: '', value: '', id: 0 }],
parameterVisible: false,
arrParameterVisible: false,
properties: props.data.valueType?.properties || [],
arrayProperties: props.data.valueType?.elementType?.properties || [],
currentParameter: {},
parameters: [],
isVirtual: props.data.expands?.virtual || props.data.expands?.virtual === 'true' ? true : false,
aggTypeList: [],
isUseWindow: props.data.expands?.virtualRule?.type === 'window' ? true : false,
isTimeWindow: props.data.expands?.virtualRule?.windowType === 'time' ? true : false,
windows: []
};
const {
form: { getFieldDecorator, getFieldsValue }
} = props;
const [dataType, setDataType] = useState(initState.dataType);
const [enumData, setEnumData] = useState(initState.enumData);
const [arrayEnumData, setArrayEnumData] = useState(initState.arrayEnumData);
const [parameterVisible, setParameterVisible] = useState(initState.parameterVisible);
const [arrParameterVisible, setArrParameterVisible] = useState(initState.arrParameterVisible);
const [properties, setProperties] = useState(initState.properties);
const [arrayProperties, setArrayProperties] = useState(initState.arrayProperties);
const [currentParameter, setCurrentParameter] = useState(initState.currentParameter);
const [configMetadata, setConfigMetadata] = useState<any[]>([]);
const [loadConfig, setLoadConfig] = useState<boolean>(false);
const [aType, setAType] = useState<string>(initState.aType);
const [isVirtual, setIsVirtual] = useState(initState.data.expands?.source === 'rule' ? true : false);
const [aggTypeList, setAggTypeList] = useState(initState.aggTypeList);
const [isUseWindow, setIsUseWindow] = useState(initState.isUseWindow);
const [isTimeWindow, setIsTimeWindow] = useState(initState.isTimeWindow);
const [windows, setWindows] = useState(initState.windows);
const [script, setScript] = useState(props.data.expands?.virtualRule?.script || '');
useEffect(() => {
if (dataType === 'enum') {
const elements = props.data.valueType?.elements || [];
setEnumData(elements);
}
getMetadata();
getAggTypeList();
if (isUseWindow && isTimeWindow) {
setWindows(['useWindow', 'timeWindow'])
} else if (isUseWindow && !isTimeWindow) {
setWindows(['useWindow'])
} else if (!isUseWindow && isTimeWindow) {
setWindows(['timeWindow'])
} else {
setWindows([]);
}
}, []);
const dataTypeChange = (value: string) => {
setDataType(value);
};
const getAggTypeList = () => {
apis.deviceProdcut.getAggTypeList().then(res => {
if (res.status === 200) {
setAggTypeList(res.result);
}
})
}
const getFormData = (onlySave: boolean) => {
const {
form
} = props;
form.validateFields((err: any, fieldValue: any) => {
if (err) return;
let data = fieldValue;
if (dataType === 'enum') {
data.valueType.elements = enumData;
}
if (dataType === 'object') {
data.valueType.properties = properties;
}
if (dataType === 'array' && data.valueType.elementType.type === 'object') {
data.valueType.elementType.properties = arrayProperties;
}
if (version === 'pro' && isVirtual) {
data.expands.virtualRule.type = isUseWindow ? 'window' : 'script';
if (isUseWindow) {
data.expands.virtualRule.windowType = isTimeWindow ? 'time' : 'num';
data.windows = undefined;
data.expands.virtualRule.script = script;
}else{
data.expands.virtualRule.script = script;
}
}
props.save({ ...data }, onlySave);
});
};
let dataSource = [{
text: 'String类型的UTC时间戳 (毫秒)',
value: 'string',
}, 'yyyy-MM-dd', 'yyyy-MM-dd HH:mm:ss', 'yyyy-MM-dd HH:mm:ss EE', 'yyyy-MM-dd HH:mm:ss zzz'];
const renderAType = () => {
switch (aType) {
case 'float':
case 'double':
return (
<div>
<Form.Item label="精度">
{getFieldDecorator('valueType.elementType.scale', {
// initialValue: initState.data.valueType?.scale,
})(<InputNumber precision={0} min={0} step={1} placeholder="小数点位数" style={{ width: '100%' }} />)}
</Form.Item>
<Form.Item label="单位">
{getFieldDecorator('valueType.elementType.unit', {
// initialValue: initState.data.valueType?.unit,
})(renderUnit(props.unitsData))}
</Form.Item>
</div>
);
case 'int':
case 'long':
return (
<div>
<Form.Item label="单位">
{getFieldDecorator('valueType.elementType.unit', {
initialValue: initState.data.valueType?.elementType?.unit,
})(renderUnit(props.unitsData))}
</Form.Item>
</div>
);
case 'string':
return (
<div>
<Form.Item label="最大长度">
{getFieldDecorator('valueType.elementType.expands.maxLength', {
initialValue: initState.data.valueType?.elementType.expands?.maxLength,
})(<Input />)}
</Form.Item>
</div>
);
case 'boolean':
return (
<div>
<Form.Item label="布尔值" style={{ height: 69 }}>
<Col span={11}>
{getFieldDecorator('valueType.elementType.trueText', {
initialValue: initState.data.valueType?.elementType.trueText || '是',
})(<Input placeholder="trueText" />)}
</Col>
<Col span={2} push={1}>
~
</Col>
<Col span={11}>
<Form.Item>
{getFieldDecorator('valueType.elementType.trueValue', {
initialValue: initState.data.valueType?.elementType.trueValue || true,
})(<Input placeholder="trueValue" />)}
</Form.Item>
</Col>
</Form.Item>
<Form.Item style={{ height: 69 }}>
<Col span={11}>
{getFieldDecorator('valueType.elementType.falseText', {
initialValue: initState.data.valueType?.elementType.falseText || '否',
})(<Input placeholder="falseText" />)}
</Col>
<Col span={2} push={1}>
~
</Col>
<Col span={11}>
<Form.Item>
{getFieldDecorator('valueType.elementType.falseValue', {
initialValue: initState.data.valueType?.elementType.falseValue || false,
})(<Input placeholder="falseValue" />)}
</Form.Item>
</Col>
</Form.Item>
</div>
);
case 'date':
return (
<div>
<Form.Item label="时间格式">
{getFieldDecorator('valueType.elementType.format', {
initialValue: initState.data.valueType?.elementType.format,
})(
<AutoComplete dataSource={dataSource} placeholder="默认格式:String类型的UTC时间戳 (毫秒)"
filterOption={(inputValue, option) =>
option?.props?.children?.toUpperCase()?.indexOf(inputValue.toUpperCase()) !== -1
}
/>,
)}
</Form.Item>
</div>
);
case 'enum':
return (
<div>
<Form.Item label="枚举项">
{arrayEnumData.map((item, index) => (
<Row key={item.id}>
<Col span={10}>
<Input
placeholder="标识"
value={item.value}
onChange={event => {
arrayEnumData[index].value = event.target.value;
setArrayEnumData([...arrayEnumData]);
}}
/>
</Col>
<Col span={1} style={{ textAlign: 'center' }}>
<Icon type="arrow-right" />
</Col>
<Col span={10}>
<Input
placeholder="对该枚举项的描述"
value={item.text}
onChange={event => {
arrayEnumData[index].text = event.target.value;
setArrayEnumData([...arrayEnumData]);
}}
/>
</Col>
<Col span={3} style={{ textAlign: 'center' }}>
{index === 0 ? (
(arrayEnumData.length - 1) === 0 ? (
<Icon type="plus-circle"
onClick={() => {
setArrayEnumData([...arrayEnumData, { id: arrayEnumData.length + 1 }]);
}}
/>
) : (
<Icon type="minus-circle"
onClick={() => {
arrayEnumData.splice(index, 1);
setArrayEnumData([...arrayEnumData]);
}}
/>
)
) : (
index === (arrayEnumData.length - 1) ? (
<Row>
<Icon type="plus-circle"
onClick={() => {
setArrayEnumData([...arrayEnumData, { id: arrayEnumData.length + 1 }]);
}}
/>
<Icon style={{ paddingLeft: 10 }}
type="minus-circle"
onClick={() => {
arrayEnumData.splice(index, 1);
setArrayEnumData([...arrayEnumData]);
}}
/>
</Row>
) : (
<Icon type="minus-circle"
onClick={() => {
arrayEnumData.splice(index, 1);
setArrayEnumData([...arrayEnumData]);
}}
/>
)
)}
</Col>
</Row>
))}
</Form.Item>
</div>
);
case 'object':
return (
<Form.Item label="JSON对象">
{arrayProperties.length > 0 && (
<List
bordered
dataSource={arrayProperties}
renderItem={(item: any) => (
<List.Item
actions={[
<Button
type="link"
onClick={() => {
setArrParameterVisible(true);
setCurrentParameter(item);
}}
>
编辑
</Button>,
<Button
type="link"
onClick={() => {
const index = arrayProperties.findIndex((i: any) => i.id === item.id);
arrayProperties.splice(index, 1);
setArrayProperties([...arrayProperties]);
}}
>
删除
</Button>,
]}
>
参数名称:{item.name}
</List.Item>
)}
/>
)}
<Button
type="link"
onClick={() => {
setCurrentParameter({});
setArrParameterVisible(true);
}}
>
<Icon type="plus" />
添加参数
</Button>
</Form.Item>
);
case 'file':
return (
<Form.Item label="文件类型">
{getFieldDecorator('valueType.elementType.fileType', {
initialValue: initState.data.valueType?.elementType.fileType,
})(
<Select>
<Select.Option value="url">URL(链接)</Select.Option>
<Select.Option value="base64">Base64(Base64编码)</Select.Option>
<Select.Option value="binary">Binary(二进制)</Select.Option>
</Select>,
)}
</Form.Item>
);
case 'password':
return (
<div>
<Form.Item label="密码长度">
{getFieldDecorator('valueType.elementType.expands.maxLength', {
initialValue: initState.data.valueType?.elementType?.expands?.maxLength,
})(<Input addonAfter="字节" />)}
</Form.Item>
</div>
);
default:
return null;
}
}
const renderDataType = (type?: string) => {
switch (type || dataType) {
case 'float':
case 'double':
return (
<div>
{/* <Form.Item label="取值范围" style={{height: 69}}>
<Col span={11}>
{getFieldDecorator('valueType.min', {
initialValue: initState.data.valueType?.min,
})(<InputNumber style={{width:'100%'}} placeholder="最小值"/>)}
</Col>
<Col span={2} push={1}>
~
</Col>
<Col span={11}>
<Form.Item>
{getFieldDecorator('valueType.max', {
initialValue: initState.data.valueType?.max,
})(<InputNumber style={{width:'100%'}} placeholder="最大值"/>)}
</Form.Item>
</Col>
</Form.Item>
<Form.Item label="步长">
{getFieldDecorator('valueType.step', {
initialValue: initState.data.valueType?.step,
})(<InputNumber style={{width:'100%'}} placeholder="请输入步长"/>)}
</Form.Item> */}
<Form.Item label="精度">
{getFieldDecorator('valueType.scale', {
initialValue: initState.data.valueType?.scale,
})(<InputNumber precision={0} min={0} step={1} placeholder="小数点位数" style={{ width: '100%' }} />)}
</Form.Item>
<Form.Item label="单位">
{getFieldDecorator('valueType.unit', {
initialValue: initState.data.valueType?.unit,
})(renderUnit(props.unitsData))}
</Form.Item>
</div>
);
case 'int':
case 'long':
return (
<div>
{/* <Form.Item label="取值范围" style={{ height: 69 }}>
<Col span={11}>
{getFieldDecorator('valueType.min', {
initialValue: initState.data.valueType?.min,
})(<InputNumber style={{ width: '100%' }} placeholder="最小值" />)}
</Col>
<Col span={2} push={1}>
~
</Col>
<Col span={11}>
<Form.Item>
{getFieldDecorator('valueType.max', {
initialValue: initState.data.valueType?.max,
})(<InputNumber style={{ width: '100%' }} placeholder="最大值" />)}
</Form.Item>
</Col>
</Form.Item>
<Form.Item label="步长">
{getFieldDecorator('valueType.step', {
initialValue: initState.data.valueType?.step,
})(<InputNumber style={{ width: '100%' }} placeholder="请输入步长" />)}
</Form.Item> */}
<Form.Item label="单位">
{getFieldDecorator('valueType.unit', {
initialValue: initState.data.valueType?.unit,
})(renderUnit(props.unitsData))}
</Form.Item>
</div>
);
case 'string':
return (
<div>
<Form.Item label="最大长度">
{getFieldDecorator('valueType.expands.maxLength', {
initialValue: initState.data.valueType?.expands?.maxLength,
})(<Input />)}
</Form.Item>
</div>
);
case 'boolean':
return (
<div>
<Form.Item label="布尔值" style={{ height: 69 }}>
<Col span={11}>
{getFieldDecorator('valueType.trueText', {
initialValue: initState.data.valueType?.trueText || '是',
})(<Input placeholder="trueText" />)}
</Col>
<Col span={2} push={1}>
~
</Col>
<Col span={11}>
<Form.Item>
{getFieldDecorator('valueType.trueValue', {
initialValue: initState.data.valueType?.trueValue || true,
})(<Input placeholder="trueValue" />)}
</Form.Item>
</Col>
</Form.Item>
<Form.Item style={{ height: 69 }}>
<Col span={11}>
{getFieldDecorator('valueType.falseText', {
initialValue: initState.data.valueType?.falseText || '否',
})(<Input placeholder="falseText" />)}
</Col>
<Col span={2} push={1}>
~
</Col>
<Col span={11}>
<Form.Item>
{getFieldDecorator('valueType.falseValue', {
initialValue: initState.data.valueType?.falseValue || false,
})(<Input placeholder="falseValue" />)}
</Form.Item>
</Col>
</Form.Item>
</div>
);
case 'date':
return (
<div>
<Form.Item label="时间格式">
{getFieldDecorator('valueType.format', {
initialValue: initState.data.valueType?.format,
})(
<AutoComplete dataSource={dataSource} placeholder="默认格式:String类型的UTC时间戳 (毫秒)"
filterOption={(inputValue, option) =>
option?.props?.children?.toUpperCase()?.indexOf(inputValue.toUpperCase()) !== -1
}
/>,
)}
</Form.Item>
</div>
);
case 'array':
return (
<div>
<Form.Item label="元素类型">
{getFieldDecorator('valueType.elementType.type', {
rules: [{ required: true, message: '请选择' }],
initialValue: initState.data.valueType?.elementType?.type,
})(
<Select
placeholder="请选择"
onChange={(value: string) => {
setAType(value);
getMetadata(undefined, value)
}}
>
<Select.OptGroup label="基本类型">
<Select.Option value="int">int(整数型)</Select.Option>
<Select.Option value="long">long(长整数型)</Select.Option>
<Select.Option value="float">float(单精度浮点型)</Select.Option>
<Select.Option value="double">double(双精度浮点数)</Select.Option>
<Select.Option value="string">text(字符串)</Select.Option>
<Select.Option value="boolean">bool(布尔型)</Select.Option>
</Select.OptGroup>
<Select.OptGroup label="其他类型">
<Select.Option value="date">date(时间型)</Select.Option>
<Select.Option value="enum">enum(枚举)</Select.Option>
<Select.Option value="object">object(结构体)</Select.Option>
<Select.Option value="file">file(文件)</Select.Option>
<Select.Option value="password">password(密码)</Select.Option>
<Select.Option value="geoPoint">geoPoint(地理位置)</Select.Option>
</Select.OptGroup>
</Select>
)}
</Form.Item>
{renderAType()}
</div>
);
case 'enum':
return (
<div>
<Form.Item label="枚举项">
{enumData.map((item, index) => (
<Row key={item.id}>
<Col span={10}>
<Input
placeholder="标识"
value={item.value}
onChange={event => {
enumData[index].value = event.target.value;
setEnumData([...enumData]);
}}
/>
</Col>
<Col span={1} style={{ textAlign: 'center' }}>
<Icon type="arrow-right" />
</Col>
<Col span={10}>
<Input
placeholder="对该枚举项的描述"
value={item.text}
onChange={event => {
enumData[index].text = event.target.value;
setEnumData([...enumData]);
}}
/>
</Col>
<Col span={3} style={{ textAlign: 'center' }}>
{index === 0 ? (
(enumData.length - 1) === 0 ? (
<Icon type="plus-circle"
onClick={() => {
setEnumData([...enumData, { id: enumData.length + 1 }]);
}}
/>
) : (
<Icon type="minus-circle"
onClick={() => {
enumData.splice(index, 1);
setEnumData([...enumData]);
}}
/>
)
) : (
index === (enumData.length - 1) ? (
<Row>
<Icon type="plus-circle"
onClick={() => {
setEnumData([...enumData, { id: enumData.length + 1 }]);
}}
/>
<Icon style={{ paddingLeft: 10 }}
type="minus-circle"
onClick={() => {
enumData.splice(index, 1);
setEnumData([...enumData]);
}}
/>
</Row>
) : (
<Icon type="minus-circle"
onClick={() => {
enumData.splice(index, 1);
setEnumData([...enumData]);
}}
/>
)
)}
</Col>
</Row>
))}
</Form.Item>
</div>
);
case 'object':
return (
<Form.Item label="JSON对象">
{properties.length > 0 && (
<List
bordered
dataSource={properties}
renderItem={(item: any) => (
<List.Item
actions={[
<Button
type="link"
onClick={() => {
setParameterVisible(true);
setCurrentParameter(item);
}}
>
编辑
</Button>,
<Button
type="link"
onClick={() => {
const index = properties.findIndex((i: any) => i.id === item.id);
properties.splice(index, 1);
setProperties([...properties]);
}}
>
删除
</Button>,
]}
>
参数名称:{item.name}
</List.Item>
)}
/>
)}
<Button
type="link"
onClick={() => {
setCurrentParameter({});
setParameterVisible(true);
}}
>
<Icon type="plus" />
添加参数
</Button>
</Form.Item>
);
case 'file':
return (
<Form.Item label="文件类型">
{getFieldDecorator('valueType.fileType', {
initialValue: initState.data.valueType?.fileType,
})(
<Select>
<Select.Option value="url">URL(链接)</Select.Option>
<Select.Option value="base64">Base64(Base64编码)</Select.Option>
<Select.Option value="binary">Binary(二进制)</Select.Option>
</Select>,
)}
</Form.Item>
);
case 'password':
return (
<div>
<Form.Item label="密码长度">
{getFieldDecorator('valueType.expands.maxLength', {
initialValue: initState.data.valueType?.expands?.maxLength,
})(<Input addonAfter="字节" />)}
</Form.Item>
</div>
);
default:
return null;
}
};
const product = useContext<any>(ProductContext);
const getMetadata = (id?: any, type?: any) => {
const data = getFieldsValue(['id', 'valueType.type']);
if (id) {
data.id = id;
}
if (type) {
data.valueType.type = type;
}
if (data.id && data.valueType.type) {
setLoadConfig(true);
apis.deviceProdcut.configMetadata({
productId: product.id,
modelType: 'property',
modelId: data.id,
typeId: data.valueType.type
}).then(rsp => {
setLoadConfig(false);
setConfigMetadata(rsp.result);
}).finally(() => setLoadConfig(false));
}
}
const renderItem = (config: any) => {
switch (config.type.type) {
case 'int':
case 'string':
return <Input />
case 'enum':
return (
<Select>
{config.type.elements.map((i: any) => (
<Select.Option value={i.value} key={i.value}>{i.text}</Select.Option>
))}
</Select>
);
default:
return <Input />
}
}
const renderConfigMetadata = () => {
return (
<Collapse>{
(configMetadata || []).map((item, index) => {
return (
<Collapse.Panel header={item.name} key={index}>
{item.properties.map((config: any) => (
<Form.Item label={config.name} key={config.property}>
{getFieldDecorator('expands.' + config.property, {
initialValue: (initState.data?.expands || {})[config.property]
})(renderItem(config))}
</Form.Item>
))}
</Collapse.Panel>
)
})}</Collapse>
)
}
const menu = (
<Menu>
<Menu.Item key="1">
<Button type="default" onClick={() => {
getFormData(true);
}}>
仅保存
</Button>
</Menu.Item>
<Menu.Item key="2">
<Button onClick={() => {
getFormData(false);
}}>保存并生效</Button>
</Menu.Item>
</Menu>
);
return (
<div>
<Drawer
title={!initState.data.id ? `添加属性` : `编辑属性`}
placement="right"
closable={false}
onClose={() => props.close()}
visible
width="30%"
>
<Spin spinning={loadConfig}>
<Form>
<Form.Item label="属性标识">
{getFieldDecorator('id', {
rules: [
{ required: true, message: '请输入属性标识' },
{ max: 64, message: '属性标识不超过64个字符' },
{ pattern: new RegExp(/^[0-9a-zA-Z_\-]+$/, "g"), message: '属性标识只能由数字、字母、下划线、中划线组成' }
],
initialValue: initState.data.id,
})(
<Input
onBlur={(value) => getMetadata(value.target.value, undefined)}
disabled={!!initState.data.id}
style={{ width: '100%' }}
placeholder="请输入属性标识"
/>,
)}
</Form.Item>
<Form.Item label="属性名称">
{getFieldDecorator('name', {
rules: [
{ required: true, message: '请输入属性名称' },
{ max: 200, message: '属性名称不超过200个字符' }
],
initialValue: initState.data.name,
})(<Input style={{ width: '100%' }} placeholder="请输入属性名称" />)}
</Form.Item>
<Form.Item label="数据类型">
{getFieldDecorator('valueType.type', {
rules: [{ required: true, message: '请选择' }],
initialValue: initState.data.valueType?.type,
})(
<Select
placeholder="请选择"
onChange={(value: string) => {
dataTypeChange(value);
getMetadata(undefined, value)
}}
>
<Select.OptGroup label="基本类型">
<Select.Option value="int">int(整数型)</Select.Option>
<Select.Option value="long">long(长整数型)</Select.Option>
<Select.Option value="float">float(单精度浮点型)</Select.Option>
<Select.Option value="double">double(双精度浮点数)</Select.Option>
<Select.Option value="string">text(字符串)</Select.Option>
<Select.Option value="boolean">bool(布尔型)</Select.Option>
</Select.OptGroup>
<Select.OptGroup label="其他类型">
<Select.Option value="date">date(时间型)</Select.Option>
<Select.Option value="enum">enum(枚举)</Select.Option>
<Select.Option value="array">array(数组)</Select.Option>
<Select.Option value="object">object(结构体)</Select.Option>
<Select.Option value="file">file(文件)</Select.Option>
<Select.Option value="password">password(密码)</Select.Option>
<Select.Option value="geoPoint">geoPoint(地理位置)</Select.Option>
</Select.OptGroup>
</Select>,
)}
</Form.Item>
{renderDataType()}
<Form.Item label="是否只读">
{getFieldDecorator('expands.readOnly', {
rules: [{ required: true }],
initialValue: initState.data.expands?.readOnly?.toString?.(),
})(
<Radio.Group>
<Radio value="true">是</Radio>
<Radio value="false">否</Radio>
</Radio.Group>,
)}
</Form.Item>
{/* 属性来源 */}
<Form.Item label="属性来源">
{getFieldDecorator('expands.source', {
rules: [{ required: true, message: '请选择' }],
initialValue: initState.data.expands?.source,
})(
<Select
placeholder="请选择"
onChange={(value: string) => {
if(value === 'rule'){
setIsVirtual(true);
}else{
setIsVirtual(false);
}
}}
>
<Select.Option value="device">设备</Select.Option>
<Select.Option value="manual">手动</Select.Option>
<Select.Option value="rule">规则</Select.Option>
</Select>
)}
</Form.Item>
{/* 虚拟属性 */}
{version === 'pro' && (
<>
{/* <Form.Item label="虚拟属性">
{getFieldDecorator('expands.virtual', {
rules: [{ required: true }],
initialValue: isVirtual,
})(
<Radio.Group onChange={(e) => {
let value = e.target.value;
setIsVirtual(value)
}}>
<Radio value={true}>是</Radio>
<Radio value={false}>否</Radio>
</Radio.Group>,
)}
</Form.Item> */}
{isVirtual && (
<>
<Form.Item wrapperCol={{ span: 24 }}>
{getFieldDecorator('expands.virtualRule.script', {
// rules: [{ required: true }],
initialValue: initState.data.expands?.virtualRule?.script
})(
<VirtualEditorComponent scriptValue={(value: string) => {
setScript(value);
}} metaDataList={props.dataList} data={props.data} formData={getFieldsValue()} />
)}
</Form.Item>
<Form.Item label="">
{getFieldDecorator('windows', {
initialValue: windows,
})(
<Checkbox.Group onChange={(value) => {
setIsUseWindow(value.includes('useWindow'));
setIsTimeWindow(value.includes('timeWindow'));
}}>
<Row gutter={24}>
<Col span={isUseWindow ? 12 : 24}>
<Checkbox value="useWindow" style={{ lineHeight: '32px' }}>使用窗口</Checkbox>
</Col>
<Col span={12}>
{isUseWindow && <Checkbox value="timeWindow" style={{ lineHeight: '32px' }}>时间窗口</Checkbox>}
</Col>
</Row>
</Checkbox.Group>
)}
</Form.Item>
{isUseWindow && (
<>
<Form.Item label="聚合函数">
{getFieldDecorator('expands.virtualRule.aggType', {
rules: [{ required: true }],
initialValue: initState.data.expands?.virtualRule?.aggType
})(
<Select>
{aggTypeList.map((item: any, index: number) => (
<Select.Option value={item.value} key={index}>{`${item.value}(${item.text})`}</Select.Option>
))}
</Select>
)}
</Form.Item>
<Row>
<Col span={10}>
<Form.Item label={`窗口长度(${isTimeWindow ? '秒' : '次'})`}>
{getFieldDecorator('expands.virtualRule.window.span', {
rules: [
{ required: true }
],
initialValue: initState.data.expands?.virtualRule?.window?.span,
})(<Input placeholder="请输入" />)}
</Form.Item>
</Col>
<Col span={4}></Col>
<Col span={10}>
<Form.Item label={`步长(${isTimeWindow ? '秒' : '次'})`}>
{getFieldDecorator('expands.virtualRule.window.every', {
// rules: [
// // { required: true }
// ],
initialValue: initState.data.expands?.virtualRule?.window?.every,
})(<Input placeholder="请输入" />)}
</Form.Item>
</Col>
</Row>
</>
)}
</>
)}
</>
)}
{!loadConfig && renderConfigMetadata()}
<Form.Item label="描述">
{getFieldDecorator('description', {
initialValue: initState.data.description,
})(<Input.TextArea rows={3} />)}
</Form.Item>
</Form>
</Spin>
<div
style={{
position: 'absolute',
right: 0,
bottom: 0,
width: '100%',
borderTop: '1px solid #e9e9e9',
padding: '10px 16px',
background: '#fff',
textAlign: 'right',
}}
>
<Button
onClick={() => {
props.close();
}}
style={{ marginRight: 8 }}
>
关闭
</Button>
<Dropdown overlay={menu}>
<Button icon="menu" type="primary">
保存<Icon type="down" />
</Button>
</Dropdown>
{/* <Button
onClick={() => {
getFormData();
}}
type="primary"
>
保存
</Button> */}
</div>
{parameterVisible && (
<Paramter
save={item => {
const index = properties.findIndex((e: any) => e.id === item.id);
if (index === -1) {
properties.push(item);
} else {
properties[index] = item;
}
setProperties(properties);
}}
unitsData={props.unitsData}
close={() => {
setCurrentParameter({});
setParameterVisible(false);
}}
data={currentParameter}
/>
)}
{arrParameterVisible && (
<Paramter
save={item => {
const index = arrayProperties.findIndex((e: any) => e.id === item.id);
if (index === -1) {
arrayProperties.push(item);
} else {
arrayProperties[index] = item;
}
setArrayProperties(arrayProperties);
}}
unitsData={props.unitsData}
close={() => {
setCurrentParameter({});
setArrParameterVisible(false);
}}
data={currentParameter}
/>
)}
</Drawer>
</div>
);
}
Example #19
Source File: descriptionFormatter.ts From prettier-plugin-jsdoc with MIT License | 4 votes |
/**
* Trim, make single line with capitalized text. Insert dot if flag for it is
* set to true and last character is a word character
*
* @private
*/
function formatDescription(
tag: string,
text: string,
options: AllOptions,
formatOptions: FormatOptions,
): string {
if (!text) return text;
const { printWidth } = options;
const { tagStringLength = 0, beginningSpace } = formatOptions;
/**
* change list with dash to dot for example:
* 1- a thing
*
* to
*
* 1. a thing
*/
text = text.replace(/^(\d+)[-][\s|]+/g, "$1. "); // Start
text = text.replace(/\n+(\s*\d+)[-][\s]+/g, "\n$1. ");
const fencedCodeBlocks = text.matchAll(/```\S*?\n[\s\S]+?```/gm);
const indentedCodeBlocks = text.matchAll(
/^\r?\n^(?:(?:(?:[ ]{4}|\t).*(?:\r?\n|$))+)/gm,
);
const allCodeBlocks = [...fencedCodeBlocks, ...indentedCodeBlocks];
const tables: string[] = [];
text = text.replace(
/((\n|^)\|[\s\S]*?)((\n[^|])|$)/g,
(code, _1, _2, _3, _, offs: number) => {
// If this potential table is inside a code block, don't touch it
for (const block of allCodeBlocks) {
if (
block.index !== undefined &&
block.index <= offs + 1 &&
offs + code.length + 1 <= block.index + block[0].length
) {
return code;
}
}
code = _3 ? code.slice(0, -1) : code;
tables.push(code);
return `\n\n${TABLE}\n\n${_3 ? _3.slice(1) : ""}`;
},
);
if (
options.jsdocCapitalizeDescription &&
!TAGS_PEV_FORMATE_DESCRIPTION.includes(tag)
) {
text = capitalizer(text);
}
text = `${tagStringLength ? `${"!".repeat(tagStringLength - 1)}?` : ""}${
text.startsWith("```") ? "\n" : ""
}${text}`;
let tableIndex = 0;
const rootAst = fromMarkdown(text);
function stringifyASTWithoutChildren(
mdAst: Content | Root,
intention: string,
parent: Content | Root | null,
) {
if (mdAst.type === "inlineCode") {
return `\`${mdAst.value}\``;
}
if (mdAst.type === "code") {
let result = mdAst.value || "";
let _intention = intention;
if (result) {
// Remove two space from lines, maybe added previous format
if (mdAst.lang) {
const supportParsers = parserSynonyms(mdAst.lang.toLowerCase());
const parser = supportParsers?.includes(options.parser as any)
? options.parser
: supportParsers?.[0] || mdAst.lang;
result = formatCode(result, intention, {
...options,
parser,
jsdocKeepUnParseAbleExampleIndent: true,
});
} else if (options.jsdocPreferCodeFences || false) {
result = formatCode(result, _intention, {
...options,
jsdocKeepUnParseAbleExampleIndent: true,
});
} else {
_intention = intention + " ".repeat(4);
result = formatCode(result, _intention, {
...options,
jsdocKeepUnParseAbleExampleIndent: true,
});
}
}
const addFence = options.jsdocPreferCodeFences || !!mdAst.lang;
result = addFence ? result : result.trimEnd();
return result
? addFence
? `\n\n${_intention}\`\`\`${mdAst.lang || ""}${result}\`\`\``
: `\n${result}`
: "";
}
if ((mdAst as Text).value === TABLE) {
if (parent) {
(parent as any).costumeType = TABLE;
}
if (tables.length > 0) {
let result = tables?.[tableIndex] || "";
tableIndex++;
if (result) {
result = format(result, {
...options,
parser: "markdown",
}).trim();
}
return `${
result
? `\n\n${intention}${result.split("\n").join(`\n${intention}`)}`
: (mdAst as Text).value
}`;
}
}
if (mdAst.type === "break") {
return `\\\n`;
}
return ((mdAst as Text).value ||
(mdAst as Link).title ||
(mdAst as Image).alt ||
"") as string;
}
function stringyfy(
mdAst: Content | Root,
intention: string,
parent: Content | Root | null,
): string {
if (!Array.isArray((mdAst as Root).children)) {
return stringifyASTWithoutChildren(mdAst, intention, parent);
}
return ((mdAst as Root).children as Content[])
.map((ast, index) => {
switch (ast.type) {
case "listItem": {
let _listCount = `\n${intention}- `;
// .replace(/((?!(^))\n)/g, "\n" + _intention);
if (typeof (mdAst as List).start === "number") {
const count = index + (((mdAst as List).start as number) ?? 1);
_listCount = `\n${intention}${count}. `;
}
const _intention = intention + " ".repeat(_listCount.length - 1);
const result = stringyfy(ast, _intention, mdAst).trim();
return `${_listCount}${result}`;
}
case "list": {
let end = "";
/**
* Add empty line after list if that is end of description
* issue: {@link https://github.com/hosseinmd/prettier-plugin-jsdoc/issues/98}
*/
if (
tag !== DESCRIPTION &&
mdAst.type === "root" &&
index === mdAst.children.length - 1
) {
end = "\n";
}
return `\n${stringyfy(ast, intention, mdAst)}${end}`;
}
case "paragraph": {
const paragraph = stringyfy(ast, intention, parent);
if ((ast as any).costumeType === TABLE) {
return paragraph;
}
return `\n\n${paragraph
/**
* Break by backslash\
* issue: https://github.com/hosseinmd/prettier-plugin-jsdoc/issues/102
*/
.split("\\\n")
.map((_paragraph) => {
const links: string[] = [];
// Find jsdoc links and remove spaces
_paragraph = _paragraph.replace(
/{@(link|linkcode|linkplain)[\s](([^{}])*)}/g,
(_, tag: string, link: string) => {
links.push(link);
return `{@${tag}${"_".repeat(link.length)}}`;
},
);
_paragraph = _paragraph.replace(/\s+/g, " "); // Make single line
if (
options.jsdocCapitalizeDescription &&
!TAGS_PEV_FORMATE_DESCRIPTION.includes(tag)
)
_paragraph = capitalizer(_paragraph);
if (options.jsdocDescriptionWithDot)
_paragraph = _paragraph.replace(/([\w\p{L}])$/u, "$1."); // Insert dot if needed
let result = breakDescriptionToLines(
_paragraph,
printWidth,
intention,
);
// Replace links
result = result.replace(
/{@(link|linkcode|linkplain)([_]+)}/g,
(original: string, tag: string, underline: string) => {
const link = links[0];
if (link.length === underline.length) {
links.shift();
return `{@${tag} ${link}}`;
}
return original;
},
);
return result;
})
.join("\\\n")}`;
}
case "strong": {
return `**${stringyfy(ast, intention, mdAst)}**`;
}
case "emphasis": {
return `_${stringyfy(ast, intention, mdAst)}_`;
}
case "heading": {
return `\n\n${intention}${"#".repeat(ast.depth)} ${stringyfy(
ast,
intention,
mdAst,
)}`;
}
case "link":
case "image": {
return `[${stringyfy(ast, intention, mdAst)}](${ast.url})`;
}
case "linkReference": {
return `[${stringyfy(ast, intention, mdAst)}][${ast.label}]`;
}
case "definition": {
return `\n\n[${ast.label}]: ${ast.url}`;
}
case "blockquote": {
const paragraph = stringyfy(ast, "", mdAst);
return `${intention}> ${paragraph
.trim()
.replace(/(\n+)/g, `$1${intention}> `)}`;
}
}
return stringyfy(ast, intention, mdAst);
})
.join("");
}
let result = stringyfy(rootAst, beginningSpace, null);
result = result.replace(/^[\s\n]+/g, "");
result = result.replace(/^([!]+\?)/g, "");
return result;
}
Example #20
Source File: generate-docs.ts From graphql-eslint with MIT License | 4 votes |
function generateDocs(): void {
const result = Object.entries(rules).map(([ruleName, rule]) => {
const blocks: string[] = [`# \`${ruleName}\``, BR];
const { deprecated, docs, schema, fixable, hasSuggestions } = rule.meta;
if (deprecated) {
blocks.push('- ❗ DEPRECATED ❗');
}
const categories = asArray(docs.category);
if (docs.recommended) {
const configNames = categories.map(category => `"plugin:@graphql-eslint/${category.toLowerCase()}-recommended"`);
blocks.push(
`${Icon.RECOMMENDED} The \`"extends": ${configNames.join(
'` and `'
)}\` property in a configuration file enables this rule.`,
BR
);
}
if (fixable) {
blocks.push(
`${Icon.FIXABLE} The \`--fix\` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#--fix) can automatically fix some of the problems reported by this rule.`,
BR
);
}
if (hasSuggestions) {
blocks.push(
`${Icon.HAS_SUGGESTIONS} This rule provides [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions)`,
BR
);
}
const { requiresSchema = false, requiresSiblings = false, graphQLJSRuleName } = docs;
blocks.push(
`- Category: \`${categories.join(' & ')}\``,
`- Rule name: \`@graphql-eslint/${ruleName}\``,
`- Requires GraphQL Schema: \`${requiresSchema}\` [ℹ️](../../README.md#extended-linting-rules-with-graphql-schema)`,
`- Requires GraphQL Operations: \`${requiresSiblings}\` [ℹ️](../../README.md#extended-linting-rules-with-siblings-operations)`,
BR,
docs.description
);
if (docs.examples?.length > 0) {
blocks.push(BR, '## Usage Examples');
for (const { usage, title, code } of docs.examples) {
const isJsCode = ['gql`', '/* GraphQL */'].some(str => code.includes(str));
blocks.push(BR, `### ${title}`, BR, '```' + (isJsCode ? 'js' : 'graphql'));
if (!isJsCode) {
const options =
usage?.length > 0
? // ESLint RuleTester accept options as array but in eslintrc config we must provide options as object
format(JSON.stringify(['error', ...usage]), {
parser: 'babel',
singleQuote: true,
printWidth: Infinity,
}).replace(';\n', '')
: "'error'";
blocks.push(`# eslint @graphql-eslint/${ruleName}: ${options}`, BR);
}
blocks.push(dedent(code), '```');
}
}
let jsonSchema = Array.isArray(schema) ? schema[0] : schema;
if (jsonSchema) {
jsonSchema =
jsonSchema.type === 'array'
? {
definitions: jsonSchema.definitions,
...jsonSchema.items,
}
: jsonSchema;
blocks.push(BR, '## Config Schema', BR, md(jsonSchema, '##'));
}
blocks.push(BR, '## Resources', BR);
if (graphQLJSRuleName) {
blocks.push(
`- [Rule source](https://github.com/graphql/graphql-js/blob/main/src/validation/rules/${graphQLJSRuleName}Rule.ts)`,
`- [Test source](https://github.com/graphql/graphql-js/tree/main/src/validation/__tests__/${graphQLJSRuleName}Rule-test.ts)`
);
} else {
blocks.push(
`- [Rule source](../../packages/plugin/src/rules/${ruleName}.ts)`,
`- [Test source](../../packages/plugin/tests/${ruleName}.spec.ts)`
);
}
blocks.push(BR);
return {
path: resolve(DOCS_PATH, `rules/${ruleName}.md`),
content: blocks.join('\n'),
};
});
const sortedRules = Object.entries(rules)
.filter(([, rule]) => !rule.meta.deprecated)
.sort(([a], [b]) => a.localeCompare(b))
.map(([ruleName, rule]) => {
const link = `[${ruleName}](rules/${ruleName}.md)`;
const { docs } = rule.meta;
return [
link,
docs.description.split('\n')[0],
docs.isDisabledForAllConfig ? '' : docs.recommended ? '![recommended][]' : '![all][]',
docs.graphQLJSRuleName ? Icon.GRAPHQL_JS : Icon.GRAPHQL_ESLINT,
rule.meta.hasSuggestions ? Icon.HAS_SUGGESTIONS : rule.meta.fixable ? Icon.FIXABLE : '',
];
});
result.push({
path: resolve(DOCS_PATH, 'README.md'),
content: [
'## Available Rules',
BR,
'Each rule has emojis denoting:',
BR,
`- ${Icon.GRAPHQL_ESLINT} \`graphql-eslint\` rule`,
`- ${Icon.GRAPHQL_JS} \`graphql-js\` rule`,
`- ${Icon.FIXABLE} if some problems reported by the rule are automatically fixable by the \`--fix\` [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) option`,
`- ${Icon.HAS_SUGGESTIONS} if some problems reported by the rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions)`,
BR,
'<!-- ? IMPORTANT! Do not manually modify this table. Run: `yarn generate:docs` -->',
printMarkdownTable(
[
`Name${NBSP.repeat(20)}`,
'Description',
{ name: `${NBSP.repeat(4)}Config${NBSP.repeat(4)}`, align: 'center' },
{ name: `${Icon.GRAPHQL_ESLINT}${NBSP}/${NBSP}${Icon.GRAPHQL_JS}`, align: 'center' },
{ name: `${Icon.FIXABLE}${NBSP}/${NBSP}${Icon.HAS_SUGGESTIONS}`, align: 'center' },
],
sortedRules
),
'[recommended]: https://img.shields.io/badge/-recommended-green.svg',
'[all]: https://img.shields.io/badge/-all-blue.svg',
BR,
].join('\n'),
});
for (const r of result) {
writeFileSync(r.path, r.content);
}
// eslint-disable-next-line no-console
console.log('✅ Documentation generated');
}