├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── .prettierrc
├── README.md
├── __tests__
├── async.test.js
├── decorator.test.js
├── eslintJs.test.js
├── import.test.js
├── index.test.js
├── nullish.test.js
├── optionalChaining.test.js
└── suite
│ ├── Component.tsx
│ ├── fix.jsx
│ ├── index.ts
│ ├── sub
│ └── Component.tsx
│ └── test.jsx
├── package.json
├── src
├── babel-decorator-plugin.ts
├── eslintJs.ts
├── index.ts
├── prettierJs.ts
├── ts2js.ts
└── typing.ts
└── tsconfig.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | __tests__/suite
2 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [require.resolve('@umijs/fabric/dist/eslint')],
3 | globals: {
4 | ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true,
5 | page: true,
6 | REACT_APP_ENV: true,
7 | },
8 | parserOptions: {
9 | tsconfigRootDir: __dirname,
10 | project: './tsconfig.json',
11 | /**
12 | * parserOptions.createDefaultProgram
13 | * Default .false
14 | * This option allows you to request that when the setting is specified,
15 | * files will be allowed when not included in the projects defined by the provided files.
16 | * Using this option will incur significant performance costs.
17 | * This option is primarily included for backwards-compatibility.
18 | * See the project section above for more information.projecttsconfig.json
19 | */
20 | createDefaultProgram: true,
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib/
3 | __tests__/tmp*
4 | yarn.lock
5 | yarn-error.log
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | __tests__
2 | src
3 | node_modules
4 | .gitignore
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "printWidth": 100,
5 | "proseWrap": "never",
6 | "overrides": [
7 | {
8 | "files": ".prettierrc",
9 | "options": {
10 | "parser": "json"
11 | }
12 | }
13 | ]
14 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sylvanas
2 |
3 | A tool to convert TypeScript to JavaScript with human-like code style.
4 |
5 | ## How to use
6 |
7 | ```bash
8 | npm install --save-dev sylvanas
9 | ```
10 |
11 | ### sylvanas(files: string[], option?: Option)
12 |
13 | ```js
14 | const sylvanas = require('sylvanas');
15 |
16 | const files = glob.sync('**/*.@(ts|tsx)');
17 |
18 | const fileList = sylvanas(files);
19 |
20 | fileList.forEach(({ data }) => {
21 | console.log('Trans:', data);
22 | });
23 | ```
24 |
25 | ### Option
26 |
27 | #### cwd - string
28 |
29 | The current working directory in which to search. Defaults to `process.cwd()`.
30 |
31 | #### action - `none` | `write` | `overwrite`
32 |
33 | Default `none`. Set what will Sylvanas do with files:
34 |
35 | - `write`: Write new file with name of suffix `.js` or `.jsx`.
36 | - `overwrite`: Like `write` but will remove origin files.
37 |
38 | #### outDir - string
39 |
40 | Set the write file folder. Defaults to `cwd`.
41 |
42 | #### decoratorsBeforeExport - boolean
43 |
44 | Same as [babel decoratorsbeforeexport](https://babeljs.io/docs/en/babel-plugin-proposal-decorators#decoratorsbeforeexport).
45 |
--------------------------------------------------------------------------------
/__tests__/async.test.js:
--------------------------------------------------------------------------------
1 | const ts2js = require('../lib/ts2js').default;
2 |
3 | function parse(text, option) {
4 | const parsed = ts2js([{ data: text.trim() }], option)[0].data;
5 | return parsed;
6 | }
7 |
8 | describe('async', () => {
9 | it('class', () => {
10 | const text = parse(
11 | `
12 | async function test() {
13 | const a = await 233;
14 | }
15 | `,
16 | );
17 | expect(text.includes('async')).toBeTruthy();
18 | expect(text.includes('await')).toBeTruthy();
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/__tests__/decorator.test.js:
--------------------------------------------------------------------------------
1 | const ts2js = require('../lib/ts2js').default;
2 |
3 | function parse(text, option) {
4 | const parsed = ts2js([{ data: text.trim() }], option)[0].data;
5 | return parsed;
6 | }
7 |
8 | describe('decorator', () => {
9 | it('class', () => {
10 | const text = parse(
11 | `
12 | // With Class
13 | @withClass
14 | class A {}
15 |
16 | @withClassVar('Light')
17 | class B {}
18 | `,
19 | );
20 | expect(text.includes('@withClass')).toBeTruthy();
21 | expect(text.includes("@withClassVar('Light')")).toBeTruthy();
22 | });
23 |
24 | it('before class', () => {
25 | const text = parse(
26 | `
27 | // With Class
28 | @withClass class A {}
29 |
30 | @withClassVar('Light') class B {}
31 | `,
32 | );
33 | expect(text.includes('@withClass')).toBeTruthy();
34 | expect(text.includes("@withClassVar('Light')")).toBeTruthy();
35 | });
36 |
37 | it('class props', () => {
38 | const text = parse(
39 | `
40 | // With Class Props
41 | class A {
42 | @withProp
43 | prop1 = 'Bamboo';
44 |
45 | @withPropVar('Light')
46 | prop2 = 'Bamboo';
47 |
48 | @withFunc
49 | func1() {}
50 |
51 | @withFuncVar('Light')
52 | func2() {}
53 | }
54 | `,
55 | );
56 |
57 | expect(text.includes('@withProp')).toBeTruthy();
58 | expect(text.includes("@withPropVar('Light')")).toBeTruthy();
59 | expect(text.includes('@withFunc')).toBeTruthy();
60 | expect(text.includes("@withFuncVar('Light')")).toBeTruthy();
61 | });
62 |
63 | describe('before export', () => {
64 | it('true', () => {
65 | const text = parse(
66 | `
67 | @decorator
68 | export class Foo {}
69 | `,
70 | {
71 | decoratorsBeforeExport: true,
72 | },
73 | );
74 |
75 | expect(text.includes('@decorator')).toBeTruthy();
76 | });
77 |
78 | it('false', () => {
79 | const text = parse(
80 | `
81 | export @decorator class Foo {}
82 | `,
83 | {
84 | decoratorsBeforeExport: false,
85 | },
86 | );
87 |
88 | expect(text.includes('@decorator')).toBeTruthy();
89 | });
90 | });
91 | });
92 |
--------------------------------------------------------------------------------
/__tests__/eslintJs.test.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const { lintAndFix } = require('../lib/eslintJs');
4 | const { parseText } = require('../lib/index');
5 |
6 | describe('lintAndFix', () => {
7 | const content = fs.readFileSync(path.resolve(__dirname, 'suite/test.jsx'), 'utf-8');
8 | const fix = fs.readFileSync(path.resolve(__dirname, 'suite/fix.jsx'), 'utf-8');
9 |
10 | it('basic', () => {
11 | const fixContent = lintAndFix(content, 'index.jsx');
12 | expect(fixContent).toEqual(fix);
13 | });
14 |
15 | it('parseText', () => {
16 | const fixContent = parseText(content, {
17 | filename: 'index.jsx',
18 | });
19 | expect(fixContent).toEqual(fix);
20 | });
21 |
22 | it('parseText no filename', () => {
23 | const fixContent = parseText(content);
24 | expect(fixContent).not.toEqual(fix);
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/__tests__/import.test.js:
--------------------------------------------------------------------------------
1 | const ts2js = require('../lib/ts2js').default;
2 |
3 | function parse(text, option) {
4 | const parsed = ts2js([{ data: text.trim() }], option)[0].data;
5 | return parsed;
6 | }
7 |
8 | describe('import', () => {
9 | it('React', () => {
10 | const text = parse(
11 | `
12 | const OtherComponent = React.lazy(() => import('./OtherComponent'));
13 |
14 | function MyComponent() {
15 | return (
16 |
17 |
18 |
19 | );
20 | }
21 | `,
22 | );
23 | expect(text.includes('React.lazy')).toBeTruthy();
24 | expect(text.includes("import('./OtherComponent')")).toBeTruthy();
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/__tests__/index.test.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs-extra');
2 | const glob = require('glob');
3 | const path = require('path');
4 | const sylvanas = require('../lib/index');
5 |
6 | const cwd = path.resolve(__dirname, 'suite');
7 |
8 | describe('sylvanas', () => {
9 | const files = glob.sync('**/*.@(ts|tsx)', {
10 | cwd,
11 | });
12 |
13 | it('basic', () => {
14 | const fileList = sylvanas(files, {
15 | cwd,
16 | });
17 |
18 | expect(fileList.length).toBe(3);
19 | });
20 |
21 | describe('write', () => {
22 | const tmpSourcePath = path.resolve(__dirname, 'tmpSrc');
23 | const tmpTargetPath = path.resolve(__dirname, 'tmpTgt');
24 |
25 | beforeEach(() => {
26 | fs.removeSync(tmpSourcePath);
27 | fs.removeSync(tmpTargetPath);
28 |
29 | fs.copySync(cwd, tmpSourcePath);
30 | });
31 |
32 | afterEach(() => {
33 | fs.removeSync(tmpSourcePath);
34 | fs.removeSync(tmpTargetPath);
35 | });
36 |
37 | it('different folder', () => {
38 | sylvanas(files, {
39 | cwd: tmpSourcePath,
40 | outDir: tmpTargetPath,
41 | action: 'write',
42 | });
43 |
44 | expect(
45 | glob.sync('**/*.@(ts|tsx|js|jsx)', {
46 | cwd: tmpTargetPath,
47 | }).length,
48 | ).toBe(3);
49 | });
50 |
51 | it('same folder', () => {
52 | sylvanas(files, {
53 | cwd: tmpSourcePath,
54 | action: 'write',
55 | });
56 |
57 | expect(
58 | glob.sync('**/*.@(ts|tsx|js|jsx)', {
59 | cwd: tmpSourcePath,
60 | }).length,
61 | ).toBe(8);
62 | });
63 |
64 | it('overwrite folder', () => {
65 | sylvanas(files, {
66 | cwd: tmpSourcePath,
67 | action: 'overwrite',
68 | });
69 |
70 | expect(
71 | glob.sync('**/*.@(ts|tsx|js|jsx)', {
72 | cwd: tmpSourcePath,
73 | }).length,
74 | ).toBe(5);
75 | });
76 | });
77 | });
78 |
--------------------------------------------------------------------------------
/__tests__/nullish.test.js:
--------------------------------------------------------------------------------
1 | const ts2js = require('../lib/ts2js').default;
2 |
3 | function parse(text, option) {
4 | const parsed = ts2js([{ data: text.trim() }], option)[0].data;
5 | return parsed;
6 | }
7 |
8 | describe('Nullish', () => {
9 | it('work', () => {
10 | const source = `
11 | interface Config {
12 | light?: number;
13 | }
14 | function my(config: Config) {
15 | return config.light ?? 2333;
16 | }
17 | `.trim();
18 | const text = parse(source);
19 | const cells = text.split(/[\r\n]+/).map((line) => line.trim());
20 | expect(cells).toEqual(['function my(config) {', 'return config.light ?? 2333;', '}']);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/__tests__/optionalChaining.test.js:
--------------------------------------------------------------------------------
1 | const ts2js = require('../lib/ts2js').default;
2 |
3 | function parse(text, option) {
4 | const parsed = ts2js([{ data: text.trim() }], option)[0].data;
5 | return parsed;
6 | }
7 |
8 | describe('optional chaining', () => {
9 | it('class', () => {
10 | const source = `
11 | const getRowByKey = (key: string, newData?: TableFormDateType[]) => (newData || data)?.filter((item) => item.key === key)[0];
12 | `.trim();
13 | const text = parse(source);
14 | expect(text).toEqual(
15 | `const getRowByKey = (key, newData) => (newData || data)?.filter(item => item.key === key)[0];`,
16 | );
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/__tests__/suite/Component.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | interface ComponentProps {
4 | title?: string;
5 | children?: React.ReactNode;
6 | }
7 |
8 | interface ComponentState {
9 | keep: boolean;
10 | }
11 |
12 | class Component extends React.Component {
13 | state = {
14 | keep: false,
15 | };
16 |
17 | render() {
18 | const { title, children } = this.props;
19 | return (
20 |
21 | {title &&
{title}
}
22 | {children}
23 |
24 | );
25 | }
26 | }
27 |
28 | export default Component;
29 |
--------------------------------------------------------------------------------
/__tests__/suite/fix.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: test
3 | * title.zh-CN: 测试
4 | * desc: test css in dependencies,[Link](https://d.umijs.org)
5 | * desc.zh-CN: 测试依赖中的 CSS,[链接](https://d.umijs.org)
6 | */
7 | import React from 'react';
8 | import katex from 'katex';
9 |
10 | export default () => Hello {typeof katex}!
;
11 |
--------------------------------------------------------------------------------
/__tests__/suite/index.ts:
--------------------------------------------------------------------------------
1 | import Component from './Component';
2 | import Component2 from './sub/Component';
3 |
4 | export function sum(...args: number[]) {
5 | let total: number = 0;
6 | args.forEach((num) => {
7 | total += num;
8 | });
9 |
10 | return total;
11 | }
12 |
13 | export default { Component, Component2 };
14 |
--------------------------------------------------------------------------------
/__tests__/suite/sub/Component.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | interface ComponentProps {
4 | title?: string;
5 | children?: React.ReactNode;
6 | }
7 |
8 | const Component: React.FunctionComponent = ({ title, children }) => {
9 | return (
10 |
11 | {title &&
{title}
}
12 | {children}
13 |
14 | );
15 | };
16 |
17 | export default Component;
18 |
--------------------------------------------------------------------------------
/__tests__/suite/test.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * title: test
3 | * title.zh-CN: 测试
4 | * desc: test css in dependencies,[Link](https://d.umijs.org)
5 | * desc.zh-CN: 测试依赖中的 CSS,[链接](https://d.umijs.org)
6 | */
7 | import React from 'react';
8 | import katex from 'katex';
9 | export default () => Hello {typeof katex}!
;
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sylvanas",
3 | "version": "0.6.1",
4 | "description": "Covert TS to JS",
5 | "repository": {
6 | "url": "https://github.com/umijs/sylvanas.git"
7 | },
8 | "license": "MIT",
9 | "author": "zombiej",
10 | "main": "lib/index.js",
11 | "scripts": {
12 | "build": "tsc",
13 | "prepublishOnly": "rm -rf lib && npm run build && np --no-cleanup --yolo --no-publish --any-branch",
14 | "prettier": "prettier --write \"**/**.{js,jsx,tsx,ts,less,md,json}\"",
15 | "test": "npm run build && jest"
16 | },
17 | "jest": {
18 | "moduleFileExtensions": [
19 | "js",
20 | "json"
21 | ],
22 | "testMatch": [
23 | "**/__tests__/**/*.test.js"
24 | ]
25 | },
26 | "dependencies": {
27 | "@babel/core": "^7.9.0",
28 | "@babel/plugin-syntax-decorators": "^7.8.3",
29 | "@babel/plugin-syntax-dynamic-import": "^7.8.3",
30 | "@babel/plugin-transform-typescript": "^7.9.4",
31 | "@types/prettier": "^1.16.4",
32 | "@umijs/fabric": "^2.2.2",
33 | "eslint": "^7.7.0",
34 | "fs-extra": "^8.0.1",
35 | "import-fresh": "^3.1.0",
36 | "prettier": "^2.1.1"
37 | },
38 | "devDependencies": {
39 | "@types/eslint": "^7.2.2",
40 | "@types/fs-extra": "^7.0.0",
41 | "@types/jest": "^26.0.10",
42 | "@types/node": "^12.0.3",
43 | "@types/react": "^16.8.19",
44 | "glob": "^7.1.4",
45 | "jest": "^24.8.0",
46 | "np": "^6.5.0",
47 | "react": "^16.8.6",
48 | "typescript": "^3.5.1"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/babel-decorator-plugin.ts:
--------------------------------------------------------------------------------
1 | import syntaxDecorators from '@babel/plugin-syntax-decorators';
2 |
3 | export default () => {
4 | return {
5 | name: 'sylvanas-decorators',
6 | inherits: syntaxDecorators,
7 | visitor: {},
8 | };
9 | };
10 |
--------------------------------------------------------------------------------
/src/eslintJs.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import { CLIEngine } from 'eslint';
3 | import { FileEntity } from './typing';
4 |
5 | const importCache = require('import-fresh');
6 |
7 | const engine = new CLIEngine({
8 | useEslintrc: false,
9 | fix: true,
10 | baseConfig: importCache(path.resolve(__dirname, '../.eslintrc.js')),
11 | resolvePluginsRelativeTo: path.resolve(__dirname, '..'),
12 | });
13 |
14 | export const lintAndFix: (content: string, filename?: string) => string = (content, filename) => {
15 | const report = engine.executeOnText(content, filename);
16 |
17 | if (report.results[0] && report.results[0].output) {
18 | return report.results[0].output;
19 | }
20 | return content;
21 | };
22 |
23 | function eslintJS(jsFiles: FileEntity[]) {
24 | const lintFiles: FileEntity[] = jsFiles.map((entity: FileEntity) => {
25 | let output: string = entity.data;
26 | try {
27 | output = lintAndFix(entity.data, entity.sourceFilePath);
28 | } catch (e) {
29 | console.error(e);
30 | }
31 | return {
32 | ...entity,
33 | data: output,
34 | };
35 | });
36 |
37 | return lintFiles;
38 | }
39 |
40 | export default eslintJS;
41 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import * as fs from 'fs-extra';
3 |
4 | import ts2js from './ts2js';
5 | import eslintJs from './eslintJs';
6 | import prettierJS from './prettierJs';
7 | import { FileEntity, BabelOption, Option, Action } from './typing';
8 |
9 | function parse(fileList: FileEntity[], option: BabelOption = {}) {
10 | // Get js from ts
11 | const jsFiles = ts2js(fileList, option);
12 |
13 | // eslint
14 | const lintFiles = eslintJs(jsFiles);
15 |
16 | // prettier
17 | const prettierFiles = prettierJS(lintFiles);
18 |
19 | return prettierFiles;
20 | }
21 |
22 | function sylvanas(files: string[], option: Option) {
23 | const cwd = option.cwd || process.cwd();
24 | const outDir = option.outDir || cwd;
25 | const action: Action = option.action || 'none';
26 |
27 | const fileList: FileEntity[] = files.map(
28 | (file): FileEntity => {
29 | const filePath = path.resolve(cwd, file);
30 | const targetFilePath = path.resolve(
31 | outDir,
32 | file.replace(/\.ts$/, '.js').replace(/\.tsx$/, '.jsx'),
33 | );
34 |
35 | return {
36 | sourceFilePath: filePath,
37 | targetFilePath,
38 | data: fs.readFileSync(filePath, 'utf8'),
39 | };
40 | },
41 | );
42 |
43 | const parsedFileList = parse(fileList, option);
44 |
45 | if (action === 'write' || action === 'overwrite') {
46 | parsedFileList.forEach(({ sourceFilePath, targetFilePath, data }) => {
47 | fs.ensureFileSync(targetFilePath);
48 | fs.writeFileSync(targetFilePath, data);
49 |
50 | if (action === 'overwrite') {
51 | fs.unlinkSync(sourceFilePath);
52 | }
53 | });
54 | }
55 |
56 | return parsedFileList;
57 | }
58 |
59 | sylvanas.parseText = function parseText(text: string, option: BabelOption = {}): string {
60 | const result = parse(
61 | [
62 | {
63 | sourceFilePath: option.filename,
64 | data: text,
65 | },
66 | ],
67 | option,
68 | );
69 |
70 | return result[0].data;
71 | };
72 |
73 | export = sylvanas;
74 |
--------------------------------------------------------------------------------
/src/prettierJs.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import * as fs from 'fs';
3 | import { format, Options } from 'prettier';
4 | import { FileEntity } from './typing';
5 |
6 | function prettierJS(jsFiles: FileEntity[]): FileEntity[] {
7 | const str = fs.readFileSync(path.resolve(__dirname, '../.prettierrc'), 'utf8');
8 | const prettierOption: Options = JSON.parse(str);
9 | prettierOption.parser = 'babel';
10 |
11 | return jsFiles.map((entity) => {
12 | let { data } = entity;
13 | try {
14 | data = format(entity.data, prettierOption);
15 | } catch (e) {
16 | console.error('error', e);
17 | }
18 | return {
19 | ...entity,
20 | data,
21 | };
22 | });
23 | }
24 |
25 | export default prettierJS;
26 |
--------------------------------------------------------------------------------
/src/ts2js.ts:
--------------------------------------------------------------------------------
1 | import { transformSync } from '@babel/core';
2 | import { FileEntity, BabelOption } from './typing';
3 | import decoratorPlugin from './babel-decorator-plugin';
4 |
5 | function ts2js(fileList: FileEntity[], option: BabelOption = {}): FileEntity[] {
6 | const jsFiles: FileEntity[] = fileList.map(
7 | (entity): FileEntity => {
8 | const { code } = transformSync(entity.data, {
9 | plugins: [
10 | [
11 | decoratorPlugin,
12 | {
13 | decoratorsBeforeExport: !!option.decoratorsBeforeExport,
14 | },
15 | ],
16 | [require.resolve('@babel/plugin-syntax-dynamic-import')],
17 | [
18 | require.resolve('@babel/plugin-transform-typescript'),
19 | {
20 | isTSX: true,
21 | },
22 | ],
23 | ],
24 | });
25 |
26 | return {
27 | ...entity,
28 | data: code,
29 | };
30 | },
31 | );
32 |
33 | return jsFiles;
34 | }
35 |
36 | export default ts2js;
37 |
--------------------------------------------------------------------------------
/src/typing.ts:
--------------------------------------------------------------------------------
1 | export type Action = 'none' | 'write' | 'overwrite';
2 |
3 | export interface BabelOption {
4 | decoratorsBeforeExport?: boolean;
5 | filename?: string;
6 | }
7 |
8 | export interface Option extends BabelOption {
9 | outDir?: string;
10 | cwd?: string;
11 | action?: Action;
12 | }
13 |
14 | export interface FileEntity {
15 | sourceFilePath: string;
16 | targetFilePath?: string;
17 | data: string;
18 | }
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "outDir": "lib",
5 | "module": "CommonJS",
6 | "target": "esnext",
7 | "lib": ["esnext"],
8 | "sourceMap": false,
9 | "baseUrl": ".",
10 | "allowSyntheticDefaultImports": true,
11 | "moduleResolution": "node",
12 | "forceConsistentCasingInFileNames": true,
13 | "noImplicitReturns": true,
14 | "suppressImplicitAnyIndexErrors": true,
15 | "noUnusedLocals": true,
16 | "experimentalDecorators": true,
17 | "rootDir": "src",
18 | "paths": {
19 | "@/*": ["./src/*"]
20 | }
21 | },
22 | "include": ["src/**/*"]
23 | }
24 |
--------------------------------------------------------------------------------