Skip to content

Commit

Permalink
Handle errors and add more unit test cases (#30)
Browse files Browse the repository at this point in the history
* Handle errors and add more unit test cases

* Improve Parser error handling

* Test ValueParser

* Helper function for ValueParser tests

* More test cases for ValueParser

* Test cases for union and basic types

* Test predefined types

* Test cases for interface and enum

* Test cases for dictionary and array

* Run tests in workflow

* Fix CI permission

* Add an option to skip invalid methods

* Lint
  • Loading branch information
zhuorantan authored Jul 27, 2021
1 parent 7dc10ed commit e5a1ea7
Show file tree
Hide file tree
Showing 14 changed files with 527 additions and 204 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ module.exports = {
"no-underscore-dangle": ["error", { "enforceInMethodNames": true, "allowAfterThis": true }],
"no-useless-constructor": "off",
"no-empty-function": ["error", {"allow": ["constructors"]}],
"object-curly-spacing": "error",
},
"overrides": [
{
Expand Down
35 changes: 23 additions & 12 deletions .github/workflows/default.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,41 @@
name: CI

# Controls when the action will run.
on:
# Triggers the workflow on push or pull request events but only for the main branch
push:
branches: [ main ]
pull_request:
branches: [ main ]

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Install dependencies
run: npm install

- name: Build
run: |
npm run build
# Steps represent a sequence of tasks that will be executed as part of the job
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Install dependencies
run: npm install

- name: Test
run: |
npm run test
lint:
runs-on: ubuntu-latest
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2

- name: Install dependencies
Expand All @@ -31,7 +46,3 @@ jobs:
echo Linking basic-types into ./src
ln -s $(pwd)/basic-types ./src/
npm run lint
- name: Build
run: |
npm run build
118 changes: 1 addition & 117 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@
"@types/chai": "^4.2.18",
"@types/mocha": "^8.2.2",
"@types/mustache": "^4.1.1",
"@types/sinon": "^10.0.1",
"@types/sinon-chai": "^3.2.5",
"@types/uuid": "^8.3.0",
"@types/yargs": "^15.0.9",
"@typescript-eslint/eslint-plugin": "^4.9.0",
Expand All @@ -44,8 +42,6 @@
"eslint-plugin-import": "^2.22.1",
"mocha": "^8.4.0",
"prettier": "^2.2.1",
"sinon": "^11.1.1",
"sinon-chai": "^3.7.0",
"ts-node": "^8.10.2",
"uuid": "^8.3.2"
},
Expand Down
1 change: 1 addition & 0 deletions src/cli/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export interface ParseConfiguration {
predefinedTypes?: string[];
defaultCustomTags?: Record<string, unknown>;
dropInterfaceIPrefix?: boolean;
skipInvalidMethods?: boolean;
}

export interface RenderConfiguration {
Expand Down
1 change: 1 addition & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ function run(): void {
predefinedTypes: new Set(config.parsing.predefinedTypes ?? []),
defaultCustomTags: config.parsing.defaultCustomTags ?? {},
dropInterfaceIPrefix: config.parsing.dropInterfaceIPrefix ?? false,
skipInvalidMethods: config.parsing.skipInvalidMethods ?? false,
});
});

Expand Down
4 changes: 3 additions & 1 deletion src/generator/CodeGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,16 @@ export class CodeGenerator {
predefinedTypes,
defaultCustomTags,
dropInterfaceIPrefix,
skipInvalidMethods,
}: {
tag: string;
interfacePaths: string[];
predefinedTypes: Set<string>;
defaultCustomTags: Record<string, unknown>;
dropInterfaceIPrefix: boolean;
skipInvalidMethods: boolean;
}): void {
const parser = new Parser(interfacePaths, predefinedTypes);
const parser = new Parser(interfacePaths, predefinedTypes, skipInvalidMethods);
const modules = parser.parse();

modules.forEach((module) => applyDefaultCustomTags(module, defaultCustomTags));
Expand Down
52 changes: 43 additions & 9 deletions src/parser/Parser.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import ts from 'typescript';
import { glob } from 'glob';
import { Module, Method, Field } from '../types';
import { Module, Method, Field, ValueType } from '../types';
import { ValueParser } from './ValueParser';
import { parseModuleJSDocTags } from './utils';
import { ParserLogger } from '../logger/ParserLogger';
import { ValueParserError } from './ValueParserError';
import { ParserError } from './ParserError';

export class Parser {
private program: ts.Program;
Expand All @@ -14,7 +16,7 @@ export class Parser {

private logger: ParserLogger;

constructor(globPatterns: string[], predefinedTypes: Set<string>) {
constructor(globPatterns: string[], predefinedTypes: Set<string>, private skipInvalidMethods: boolean = false) {
const filePaths = globPatterns.flatMap((pattern) => glob.sync(pattern));
this.program = ts.createProgram({
rootNames: filePaths,
Expand Down Expand Up @@ -63,7 +65,22 @@ export class Parser {
const interfaceName = jsDocTagsResult.overrideName ?? node.name.text;

const methods: Method[] = node.members
.map((methodNode) => this.methodFromNode(methodNode))
.map((methodNode) => {
try {
return this.methodFromNode(methodNode);
} catch (error) {
if (error instanceof ParserError) {
if (this.skipInvalidMethods) {
this.logger.warnSkippedNode(error.node, error.reason, error.guide);
return null;
}

throw error;
}

throw error;
}
})
.filter((method): method is Method => method !== null);

const documentation = ts.displayPartsToString(symbol?.getDocumentationComment(this.checker));
Expand All @@ -78,14 +95,32 @@ export class Parser {

private methodFromNode(node: ts.Node): Method | null {
if (!ts.isMethodSignature(node)) {
this.logger.warnSkippedNode(node, 'it is not valid method signature', 'Please define only methods');
return null;
throw new ParserError(node, 'it is not valid method signature', 'Please define only methods');
}

const methodName = node.name.getText();

const parameters = this.fieldsFromParameters(node);
const returnType = this.valueParser.parseFunctionReturnType(node);
let parameters: Field[];
try {
parameters = this.fieldsFromParameters(node);
} catch (error) {
if (error instanceof ValueParserError) {
throw new ParserError(node, `parameters error: ${error.message}`, error.guide);
}

throw error;
}

let returnType: ValueType | null;
try {
returnType = this.valueParser.parseFunctionReturnType(node);
} catch (error) {
if (error instanceof ValueParserError) {
throw new ParserError(node, `return type error: ${error.message}`, error.guide);
}

throw error;
}

const symbol = this.checker.getSymbolAtLocation(node.name);
const documentation = ts.displayPartsToString(symbol?.getDocumentationComment(this.checker));
Expand All @@ -105,12 +140,11 @@ export class Parser {
return [];
}
if (parameterNodes.length > 1) {
this.logger.warnSkippedNode(
throw new ParserError(
methodSignature,
'it has multiple parameters',
'Methods should only have one property. Please use destructuring object for multiple parameters'
);
throw new Error('Multiple parameters is not supported.');
}

const parameterDeclaration = parameterNodes[0];
Expand Down
7 changes: 7 additions & 0 deletions src/parser/ParserError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import ts from 'typescript';

export class ParserError extends Error {
constructor(public node: ts.Node, public reason: string, public guide: string) {
super(`${reason}. ${guide}`);
}
}
Loading

0 comments on commit e5a1ea7

Please sign in to comment.