Skip to content

Commit

Permalink
feat(@rsbuild/doctor): rsbuild doctor add rule-utils package (#220)
Browse files Browse the repository at this point in the history
Co-authored-by: easy1090 <[email protected]>
  • Loading branch information
easy1090 and easy1090 authored Oct 27, 2023
1 parent d1a2745 commit f16cba2
Show file tree
Hide file tree
Showing 17 changed files with 821 additions and 2 deletions.
6 changes: 6 additions & 0 deletions .changeset/yellow-masks-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rsbuild/doctor-utils': patch
'@rsbuild/doctor-sdk': patch
---

feat: rsbuild doctor add rule-utils package
3 changes: 3 additions & 0 deletions packages/doctor-sdk/modern.config.esm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@ export default defineConfig({
format: 'esm',
target: 'esnext',
outDir: './dist/esm',
dts: {
distPath: '../type',
},
},
});
3 changes: 3 additions & 0 deletions packages/doctor-utils/modern.config.esm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@ export default defineConfig({
format: 'esm',
target: 'esnext',
outDir: './dist/esm',
dts: {
distPath: '../type',
},
},
});
13 changes: 13 additions & 0 deletions packages/doctor-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
"types": "./dist/type/build/index.d.ts",
"require": "./dist/cjs/build/index.js",
"import": "./dist/esm/build/index.js"
},
"./ruleUtils": {
"types": "./dist/type/rule-utils/index.d.ts",
"require": "./dist/cjs/burule-utilsild/index.js",
"import": "./dist/esm/rule-utils/index.js"
}
},
"typesVersions": {
Expand All @@ -35,6 +40,9 @@
],
"build": [
"./dist/type/build/index.d.ts"
],
"ruleUtils": [
"./dist/type/rule-utils/index.d.ts"
]
}
},
Expand All @@ -54,6 +62,11 @@
"fs-extra": "^11.1.1",
"get-port": "5.1.1",
"json-stream-stringify": "3.0.1",
"acorn": "^8.10.0",
"acorn-import-assertions": "1.8.0",
"acorn-walk": "8.2.0",
"lines-and-columns": "2.0.3",
"@types/estree": "1.0.0",
"lodash": "^4.17.21"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions packages/doctor-utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './build';
export * from './common';
export * as RuleUtils from './rule-utils';
99 changes: 99 additions & 0 deletions packages/doctor-utils/src/rule-utils/document/document.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { LinesAndColumns } from 'lines-and-columns';
import { isUndefined, isNumber } from 'lodash';
import { Range, OffsetRange, Position, DocumentEditData } from './types';

/** Document Catalogue */
export class Document {
/** Actual document content. */
private _text = '';

/** Get the displacement of the file position in the text. */
positionAt!: (offset: number) => Position | undefined;

/** Get the position of the displacement point in the file. */
offsetAt!: (position: Position) => number | undefined;

constructor(content: string) {
this._text;
this._text = content;
this.createFinder();
}

/** Generate location search */
private createFinder() {
const find = new LinesAndColumns(this._text);

this.positionAt = (offset) => {
if (offset >= this._text.length) {
offset = this._text.length - 1;
}

if (offset < 0) {
offset = 0;
}

const result = find.locationForIndex(offset);

if (!result) {
return;
}

return {
line: result.line + 1,
column: result.column,
};
};

this.offsetAt = (position) => {
return (
find.indexForLocation({
line: position.line - 1,
column: position.column,
}) ?? undefined
);
};
}

getText(range?: Range | OffsetRange) {
if (!range) {
return this._text;
}

const start =
typeof range.start === 'number'
? range.start
: this.offsetAt(range.start);
const end =
typeof range.end === 'number' ? range.end : this.offsetAt(range.end);

if (isUndefined(start)) {
throw new Error(`Location ${JSON.stringify(start)} is illegal`);
}

if (isUndefined(end)) {
throw new Error(`Location ${JSON.stringify(end)} is illegal`);
}

return this._text.slice(start, end);
}

/** Edit document data */
edit(data: DocumentEditData) {
let { _text: content } = this;
const startOffset = isNumber(data.start)
? data.start
: this.offsetAt(data.start);
const endOffset = isNumber(data.end) ? data.end : this.offsetAt(data.end);

if (isUndefined(startOffset) || isUndefined(endOffset)) {
return;
}

const startTxt = content.substring(0, startOffset);
const endTxt = content.substring(endOffset, content.length);

content = startTxt + data.newText + endTxt;

return content;
}
}
3 changes: 3 additions & 0 deletions packages/doctor-utils/src/rule-utils/document/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './document';
export * from './types';
export * from './server';
18 changes: 18 additions & 0 deletions packages/doctor-utils/src/rule-utils/document/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Document } from './document';

const store = new Map<string, Document>();

/** Create Document */
export function getDocument(content: string) {
if (store.has(content)) {
return store.get(content)!;
}

const doc = new Document(content);
store.set(content, doc);
return doc;
}

export function clearDocument() {
store.clear();
}
34 changes: 34 additions & 0 deletions packages/doctor-utils/src/rule-utils/document/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Location
* - line starting point is 1
* - column starting point is 0
*/
export interface Position {
line: number;
column: number;
}

/** Location range */
export interface Range {
start: Position;
end: Position;
}

/** Offset range */
export interface OffsetRange {
start: number;
end: number;
}

/** Text repair data */
export interface DocumentEditData {
/** Modify the starting position of string in the original text */
start: number | Position;
/** Modify string in the key position of the original text */
end: number | Position;
/**
* Replaced new text
* - If empty, delete the original text
*/
newText?: string;
}
2 changes: 2 additions & 0 deletions packages/doctor-utils/src/rule-utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './document';
export * from './parser';
154 changes: 154 additions & 0 deletions packages/doctor-utils/src/rule-utils/parser/asserts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { isObject } from 'lodash';
import { Node } from './types';

function isSyntaxNode(node: unknown): node is Node.SyntaxNode {
return isObject(node) && 'type' in node;
}

function assertCreator<T>(type: string) {
return (node: unknown): node is T => {
return isSyntaxNode(node) && node.type === type;
};
}

export const asserts = {
isProgram: assertCreator<Node.Program>('Program'),
isEmptyStatement: assertCreator<Node.EmptyStatement>('EmptyStatement'),
isBlockStatement: assertCreator<Node.BlockStatement>('BlockStatement'),
isStaticBlock: assertCreator<Node.StaticBlock>('StaticBlock'),
isExpressionStatement: assertCreator<Node.ExpressionStatement>(
'ExpressionStatement',
),
isIfStatement: assertCreator<Node.IfStatement>('IfStatement'),
isLabeledStatement: assertCreator<Node.LabeledStatement>('LabeledStatement'),
isBreakStatement: assertCreator<Node.BreakStatement>('BreakStatement'),
isContinueStatement:
assertCreator<Node.ContinueStatement>('ContinueStatement'),
isWithStatement: assertCreator<Node.WithStatement>('WithStatement'),
isSwitchStatement: assertCreator<Node.SwitchStatement>('SwitchStatement'),
isReturnStatement: assertCreator<Node.ReturnStatement>('ReturnStatement'),
isThrowStatement: assertCreator<Node.ThrowStatement>('ThrowStatement'),
isTryStatement: assertCreator<Node.TryStatement>('TryStatement'),
isWhileStatement: assertCreator<Node.WhileStatement>('WhileStatement'),
isDoWhileStatement: assertCreator<Node.DoWhileStatement>('DoWhileStatement'),
isForStatement: assertCreator<Node.ForStatement>('ForStatement'),
isForInStatement: assertCreator<Node.ForInStatement>('ForInStatement'),
isForOfStatement: assertCreator<Node.ForOfStatement>('ForOfStatement'),
isDebuggerStatement:
assertCreator<Node.DebuggerStatement>('DebuggerStatement'),
isFunctionDeclaration: assertCreator<Node.FunctionDeclaration>(
'FunctionDeclaration',
),
isVariableDeclaration: assertCreator<Node.VariableDeclaration>(
'VariableDeclaration',
),
isVariableDeclarator:
assertCreator<Node.VariableDeclarator>('VariableDeclarator'),
isChainExpression: assertCreator<Node.ChainExpression>('ChainExpression'),
isThisExpression: assertCreator<Node.ThisExpression>('ThisExpression'),
isArrayExpression: assertCreator<Node.ArrayExpression>('ArrayExpression'),
isObjectExpression: assertCreator<Node.ObjectExpression>('ObjectExpression'),
isPrivateIdentifier:
assertCreator<Node.PrivateIdentifier>('PrivateIdentifier'),
isProperty: assertCreator<Node.Property>('Property'),
isPropertyDefinition:
assertCreator<Node.PropertyDefinition>('PropertyDefinition'),
isFunctionExpression:
assertCreator<Node.FunctionExpression>('FunctionExpression'),
isSequenceExpression:
assertCreator<Node.SequenceExpression>('SequenceExpression'),
isUnaryExpression: assertCreator<Node.UnaryExpression>('UnaryExpression'),
isBinaryExpression: assertCreator<Node.BinaryExpression>('BinaryExpression'),
isAssignmentExpression: assertCreator<Node.AssignmentExpression>(
'AssignmentExpression',
),
isUpdateExpression: assertCreator<Node.UpdateExpression>('UpdateExpression'),
isLogicalExpression:
assertCreator<Node.LogicalExpression>('LogicalExpression'),
isConditionalExpression: assertCreator<Node.ConditionalExpression>(
'ConditionalExpression',
),
isNewExpression: assertCreator<Node.NewExpression>('NewExpression'),
isSwitchCase: assertCreator<Node.SwitchCase>('SwitchCase'),
isCatchClause: assertCreator<Node.CatchClause>('CatchClause'),
isIdentifier: assertCreator<Node.Identifier>('Identifier'),
isLiteral: assertCreator<Node.Literal>('Literal'),
isSuper: assertCreator<Node.Super>('Super'),
isSpreadElement: assertCreator<Node.SpreadElement>('SpreadElement'),
isArrowFunctionExpression: assertCreator<Node.ArrowFunctionExpression>(
'ArrowFunctionExpression',
),
isYieldExpression: assertCreator<Node.YieldExpression>('YieldExpression'),
isTemplateLiteral: assertCreator<Node.TemplateLiteral>('TemplateLiteral'),
isTaggedTemplateExpression: assertCreator<Node.TaggedTemplateExpression>(
'TaggedTemplateExpression',
),
isTemplateElement: assertCreator<Node.TemplateElement>('TemplateElement'),
isObjectPattern: assertCreator<Node.ObjectPattern>('ObjectPattern'),
isArrayPattern: assertCreator<Node.ArrayPattern>('ArrayPattern'),
isRestElement: assertCreator<Node.RestElement>('RestElement'),
isAssignmentPattern:
assertCreator<Node.AssignmentPattern>('AssignmentPattern'),
isClassBody: assertCreator<Node.ClassBody>('ClassBody'),
isClassDeclaration: assertCreator<Node.ClassDeclaration>('ClassDeclaration'),
isClassExpression: assertCreator<Node.ClassExpression>('ClassExpression'),
isMetaProperty: assertCreator<Node.MetaProperty>('MetaProperty'),
isImportDeclaration:
assertCreator<Node.ImportDeclaration>('ImportDeclaration'),
isImportSpecifier: assertCreator<Node.ImportSpecifier>('ImportSpecifier'),
isImportExpression: assertCreator<Node.ImportExpression>('ImportExpression'),
isImportDefaultSpecifier: assertCreator<Node.ImportDefaultSpecifier>(
'ImportDefaultSpecifier',
),
isImportNamespaceSpecifier: assertCreator<Node.ImportNamespaceSpecifier>(
'ImportNamespaceSpecifier',
),
isExportNamedDeclaration: assertCreator<Node.ExportNamedDeclaration>(
'ExportNamedDeclaration',
),
isExportSpecifier: assertCreator<Node.ExportSpecifier>('ExportSpecifier'),
isExportDefaultDeclaration: assertCreator<Node.ExportDefaultDeclaration>(
'ExportDefaultDeclaration',
),
isExportAllDeclaration: assertCreator<Node.ExportAllDeclaration>(
'ExportAllDeclaration',
),
isAwaitExpression: assertCreator<Node.AwaitExpression>('AwaitExpression'),
isMethodDefinition: assertCreator<Node.MethodDefinition>('MethodDefinition'),
isMemberExpression: assertCreator<Node.MemberExpression>('MemberExpression'),

isComment(node: unknown): node is Node.Comment {
return (
isSyntaxNode(node) && (node.type === 'Line' || node.type === 'Block')
);
},
isDirective(node: unknown): node is Node.Directive {
return asserts.isExpressionStatement(node) && 'directive' in node;
},
isSimpleCallExpression(node: unknown): node is Node.SimpleCallExpression {
return isSyntaxNode(node) && node.type === 'CallExpression';
},
isAssignmentProperty(node: unknown): node is Node.AssignmentProperty {
return asserts.isProperty(node) && node.kind === 'init';
},
isSimpleLiteral(node: unknown): node is Node.SimpleLiteral {
return (
asserts.isLiteral(node) &&
!asserts.isRegExpLiteral(node) &&
!asserts.isBigIntLiteral(node)
);
},
isRegExpLiteral(node: unknown): node is Node.RegExpLiteral {
return asserts.isLiteral(node) && 'regex' in node;
},
isBigIntLiteral(node: unknown): node is Node.BigIntLiteral {
return asserts.isLiteral(node) && 'bigint' in node;
},
isExportStatement(node: unknown): node is Node.ExportStatement {
return (
asserts.isExportAllDeclaration(node) ||
asserts.isExportDefaultDeclaration(node) ||
asserts.isExportNamedDeclaration(node)
);
},
} as const;
4 changes: 4 additions & 0 deletions packages/doctor-utils/src/rule-utils/parser/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './asserts';
export * from './parser';
export * from './utils';
export * from './types';
Loading

0 comments on commit f16cba2

Please sign in to comment.