├── .eslintrc.json ├── .gitignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── codeGenerator │ ├── codeGenerator.ts │ ├── generateBodyCode.ts │ ├── tests │ │ ├── importTestCase.test.ts │ │ ├── miniTest.test.ts │ │ ├── miniTestResults │ │ │ ├── Arrays.ts │ │ │ ├── ArraysWithObject.ts │ │ │ ├── Enums.ts │ │ │ ├── Intersection.ts │ │ │ ├── NameSpace.ts │ │ │ ├── OneArray.ts │ │ │ ├── OneArrayWithObject.ts │ │ │ ├── OneObject.ts │ │ │ ├── PrimitiveArrays.ts │ │ │ ├── PrimitiveArraysWithObject.ts │ │ │ ├── PrimitiveIntersection.ts │ │ │ ├── PrimitiveOneArray.ts │ │ │ ├── PrimitiveOneArrayWithObject.ts │ │ │ ├── PrimitiveUnion.ts │ │ │ ├── PrimitiveUnionWithObject.ts │ │ │ ├── Tuple.ts │ │ │ ├── Union.ts │ │ │ ├── UnionWithObject.ts │ │ │ └── WithGenerics.ts │ │ └── temporalTest.test.ts │ ├── types │ │ └── index.ts │ └── utils.ts ├── index.ts ├── reporterAst │ ├── astUtils.ts │ └── index.ts ├── transformer │ ├── nodeParser │ │ ├── index.ts │ │ ├── parseArray.ts │ │ ├── parseClass.ts │ │ ├── parseIntersection.ts │ │ ├── parseLiteral.ts │ │ ├── parseNode.ts │ │ ├── parseObject.ts │ │ ├── parseTuple.ts │ │ └── parseUnion.ts │ ├── tests │ │ ├── importTestCase.test.ts │ │ ├── interfaceWithPrimitives.test.ts │ │ ├── minimumRequiredTestCase.test.ts │ │ └── primitiveType.test.ts │ └── transformer.ts ├── types.ts ├── utils.ts └── wrongTypeReportGenerator.ts ├── tests ├── cases │ ├── InterfaceWithPrimitives.ts │ ├── MinimumRequiredTestCase.ts │ ├── PrimitiveType.ts │ ├── adds_type_guard_import_to_source_file_and_also_exports.ts │ ├── allows_the_name_of_the_guard_file_file_to_be_specified.ts │ ├── any_and_unknown_work_in_interesction_types.ts │ ├── any_and_unknown_work_in_union_types.ts │ ├── check_if_any_callable_properties_is_a_function.ts │ ├── check_if_callable_interface_is_a_function.ts │ ├── correctly_handles_default_export.ts │ ├── deals_with_unknown_type_as_it_would_any.ts │ ├── does_not_generate_empty_guard_files.ts │ ├── does_not_touch_guardts_files_that_are_not_autogenerated.ts │ ├── duplicate_guard_re-exports.ts │ ├── generated_type_guards_for_arrays_of_any.ts │ ├── generated_type_guards_for_discriminated_unions.ts │ ├── generated_type_guards_for_enums.ts │ ├── generated_type_guards_for_intersection_type.ts │ ├── generated_type_guards_for_nested_arrays.ts │ ├── generated_type_guards_for_numeric_enums_in_optional_records.ts │ ├── generated_type_guards_with_a_short_circuit_are_correctly_stripped_by_UglifyJS.ts │ ├── generates_tuples.ts │ ├── generates_type_guards_for_a_Pick_type.ts │ ├── generates_type_guards_for_an_object_literal_type.ts │ ├── generates_type_guards_for_boolean.ts │ ├── generates_type_guards_for_dynamic_object_keys,_including_when_mixed_with_static_keys.ts │ ├── generates_type_guards_for_empty_object_if_exportAll_is_true.ts │ ├── generates_type_guards_for_interface_extending_object_type.ts │ ├── generates_type_guards_for_interface_extending_object_type_with_type_guard.ts │ ├── generates_type_guards_for_interface_extending_other_interface.ts │ ├── generates_type_guards_for_interface_extending_other_interface_with_type_guard.ts │ ├── generates_type_guards_for_interface_properties_with_numerical_names.ts │ ├── generates_type_guards_for_interface_property_with_empty_string_as_name.ts │ ├── generates_type_guards_for_interface_property_with_quoted_strings_as_names.ts │ ├── generates_type_guards_for_interface_with_optional_field.ts │ ├── generates_type_guards_for_mapped_types.ts │ ├── generates_type_guards_for_nested_interface.ts │ ├── generates_type_guards_for_nested_interface_with_type_guard.ts │ ├── generates_type_guards_for_property_with_non_alphanumeric_name.ts │ ├── generates_type_guards_for_record_types.ts │ ├── generates_type_guards_for_recursive_types.ts │ ├── generates_type_guards_for_simple_interface.ts │ ├── generates_type_guards_for_type_properties_with_numerical_names.ts │ ├── generates_type_guards_for_type_property_with_empty_string_as_name.ts │ ├── generates_type_guards_with_a_short_circuit.ts │ ├── import │ │ ├── importTestCase.ts │ │ └── toBeImported.ts │ ├── imports_and_uses_generated_type_guard_if_the_type_is_used_in_another_file.ts │ ├── mini │ │ └── miniTest.ts │ ├── works_for_any_type.ts │ └── works_for_unknown_type.ts └── utils │ ├── getSourceFile.ts │ ├── getTestCase.ts │ ├── getTypeDeclaration.ts │ └── index.ts ├── tsconfig.json └── tsconfig.prod.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "plugin:prettier/recommended" 10 | ], 11 | "overrides": [], 12 | "parser": "@typescript-eslint/parser", 13 | "parserOptions": { 14 | "ecmaVersion": "latest", 15 | "sourceType": "module" 16 | }, 17 | "plugins": ["@typescript-eslint", "prettier"], 18 | "rules": {} 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /test 3 | dist -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSpacing": true, 4 | "embeddedLanguageFormatting": "auto", 5 | "printWidth": 100, 6 | "semi": true, 7 | "singleQuote": true, 8 | "tabWidth": 4, 9 | "trailingComma": "all", 10 | "useTabs": false, 11 | "endOfLine": "lf" 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Changwoo Yoo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Motivation 2 | 3 | When your DTO is different from your backend's specification and have no way to detect it, it is hard to debug the error! 4 | Below is an example of WTF moment. 5 | ```tsx 6 | // Your DTO 7 | interface meDto { 8 | ... 9 | user: { 10 | id: number; 11 | } 12 | ... 13 | } 14 | 15 | // From your backend 16 | { 17 | ... 18 | user: "{id: 123}" // WTF?! it is too hard to find! 19 | ... 20 | } 21 | ``` 22 | 23 | So, when Backend's API response is different from our DTO, report it! 24 | 25 | # Installation 26 | 27 | `npm i wrong-type-report-generator` 28 | 29 | # Usage 30 | 31 | ### 1. Generate wrong-type-report 32 | 33 | ```tsx 34 | import { generateWrongTypeReport } from 'wrong-type-report-generator'; 35 | 36 | const files = getFiles(); 37 | 38 | await generateWrongTypeReport({ 39 | filePaths: files, 40 | outDirPath: './generated', 41 | }); 42 | ``` 43 | 44 | ### 2. Use generated reporter with Axios then send wrong type report 45 | 46 | ```tsx 47 | import { generateReporter } from 'wrong-type-report-generator'; 48 | import { validateDto } from './generated'; 49 | 50 | const report = generateReporter((errorReport) => { 51 | sendForDebug(errorReport); 52 | }); 53 | 54 | export const xxxApi = async () => { 55 | return axios.get('www.xxx.xxx').then(report(validateDto)); // use generated reporter here! 56 | }; 57 | ``` 58 | 59 | # TODO 60 | 61 | - [ ] Generation Optimization 62 | - [ ] Refactor spaghetti code 63 | - [ ] performance optimization 64 | - [ ] code generator tests 65 | - [ ] duplicated property error optimization 66 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | transform: { 4 | '^.+\\.(t|j)sx?$': '@swc/jest', 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wrong-type-report-generator", 3 | "version": "0.0.15", 4 | "description": "Automatically generate runtime wrong type report from typescript type definition", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "publish": "rm -rf dist && tsc -p tsconfig.prod.json && npm publish" 9 | }, 10 | "files": [ 11 | "dist/*" 12 | ], 13 | "keywords": [ 14 | "runtime", 15 | "runtime-type", 16 | "runtime-type-check", 17 | "runtime-type-checker", 18 | "runtime-type-checking", 19 | "runtime-type-report", 20 | "runtime-type-reporter", 21 | "runtime-type-reporting", 22 | "runtime-type-report-generator", 23 | "runtime-type-reporting-generator", 24 | "runtime-type-reporter-generator", 25 | "runtime-type-check-report", 26 | "runtime-type-check-reporter", 27 | "runtime-type-check-reporting", 28 | "type check", 29 | "typescript", 30 | "type", 31 | "report", 32 | "generator", 33 | "wrong", 34 | "type-report-generator", 35 | "wrong-type-report-generator", 36 | "typescript-type-report-generator", 37 | "typescript-wrong-type-report-generator", 38 | "typescript-type-report", 39 | "typescript-wrong-type-report" 40 | ], 41 | "author": "changwoolab", 42 | "license": "MIT", 43 | "devDependencies": { 44 | "@swc/cli": "^0.1.62", 45 | "@swc/core": "^1.3.57", 46 | "@swc/jest": "^0.2.26", 47 | "@types/jest": "^29.5.1", 48 | "@typescript-eslint/eslint-plugin": "^5.58.0", 49 | "@typescript-eslint/parser": "^5.58.0", 50 | "eslint": "^8.38.0", 51 | "jest": "^29.5.0", 52 | "prettier": "^2.8.7", 53 | "ts-morph": "^18.0.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/codeGenerator/codeGenerator.ts: -------------------------------------------------------------------------------- 1 | import { ImportDeclarationStructure, Project, SourceFile, StructureKind } from 'ts-morph'; 2 | import { AstRootNode, Dependencies } from '../reporterAst'; 3 | import { generateBodyCode } from './generateBodyCode'; 4 | import { makeAsync } from '../utils'; 5 | 6 | export type CodeGenerator = { 7 | astRootNode: AstRootNode; 8 | project: Project; 9 | outFilePath: string; 10 | inputSourceFile: SourceFile; 11 | }; 12 | 13 | /** 14 | * Generate code for type guard 15 | * 16 | * @returns Promise 17 | */ 18 | export const codeGenerator = async ({ astRootNode, project, outFilePath, inputSourceFile }: CodeGenerator) => { 19 | await makeAsync(); 20 | // Generate outfile 21 | const outFile = project.createSourceFile(outFilePath, undefined, { 22 | overwrite: true, 23 | }); 24 | 25 | // import dependencies 26 | // TODO: can be implemented via `sourceFile.fixMissingImports()`... 27 | // Should think about replacing this with that 28 | const { dependencies } = astRootNode; 29 | outFile.addImportDeclarations( 30 | getImportDeclarations({ 31 | inputSourceFile, 32 | dependencies, 33 | outFile, 34 | }), 35 | ); 36 | 37 | // generate remaining codes 38 | const { ast } = astRootNode; 39 | const pascalCasedName = ast.name[0].toUpperCase() + ast.name.slice(1); 40 | 41 | const code = [ 42 | `export const validate${pascalCasedName} = (value: unknown): GeneratedWrongTypeErrorReport | undefined => {`, 43 | ` const typedValue = value as ${ast.name};`, 44 | ` const error: GeneratedWrongTypeErrorReport = [];`, 45 | ` ${generateBodyCode({ 46 | astNode: ast, 47 | nameStack: [], 48 | propertyChainStack: [], 49 | root: true, 50 | })}`, 51 | ` return error.length === 0 ? undefined : error;`, 52 | `}`, 53 | ].join('\n'); 54 | 55 | outFile.addStatements(code); 56 | outFile.fixMissingImports(); 57 | process.env.NODE_ENV === 'production' && outFile.insertStatements(0, '// @ts-nocheck'); 58 | outFile.insertStatements(0, '/* eslint-disable */'); 59 | outFile.formatText(); 60 | 61 | await outFile.save(); 62 | }; 63 | 64 | const getImportDeclarations = ({ 65 | inputSourceFile, 66 | dependencies, 67 | outFile, 68 | }: { 69 | inputSourceFile: SourceFile; 70 | dependencies: Dependencies; 71 | outFile: SourceFile; 72 | }) => { 73 | /** 74 | * map import name to module specifier 75 | */ 76 | const importsMap = new Map(); 77 | for (const impDeclaration of inputSourceFile.getImportDeclarations()) { 78 | impDeclaration.getNamedImports().forEach((impSpecifier) => { 79 | importsMap.set(impSpecifier.getText(), impDeclaration.getModuleSpecifierValue()); 80 | }); 81 | } 82 | 83 | const importDeclarations = Array.from(dependencies.entries()).reduce((structures, [importFile, imports]) => { 84 | if (outFile === importFile) { 85 | return structures; 86 | } 87 | 88 | let moduleSpecifier = outFile.getRelativePathAsModuleSpecifierTo(importFile); 89 | 90 | if (importFile.isInNodeModules()) { 91 | // Packages within node_modules should not be referenced via relative path 92 | for (const im in imports) { 93 | const importDeclaration = importsMap.get(im); 94 | if (importDeclaration) { 95 | moduleSpecifier = importDeclaration; 96 | } 97 | } 98 | } 99 | 100 | const defaultImport = imports.default; 101 | delete imports.default; 102 | const namedImports = Object.entries(imports).map(([alias, name]) => (alias === name ? name : { name, alias })); 103 | structures.push({ 104 | defaultImport, 105 | kind: StructureKind.ImportDeclaration, 106 | moduleSpecifier, 107 | namedImports, 108 | }); 109 | return structures; 110 | }, [] as ImportDeclarationStructure[]); 111 | 112 | return importDeclarations; 113 | }; 114 | -------------------------------------------------------------------------------- /src/codeGenerator/generateBodyCode.ts: -------------------------------------------------------------------------------- 1 | import { AstNode } from '../reporterAst'; 2 | import { getName, getNewStack, wrapQuoteSymbol } from './utils'; 3 | 4 | export type GenerateBodyCode = { 5 | astNode: AstNode; 6 | namePrefix?: string; // prefix of property chain 7 | nameStack: string[]; // stack for property name -> ${namePrefix}.${namestack} 8 | propertyChainStack: string[]; // stack for property chain 9 | root?: boolean; 10 | }; 11 | 12 | /** 13 | * Traverse AST Node and generate code 14 | */ 15 | export const generateBodyCode = ({ 16 | astNode, 17 | nameStack, 18 | namePrefix, 19 | propertyChainStack, 20 | root, 21 | }: GenerateBodyCode): string => { 22 | const copiedNameStack = [...nameStack]; 23 | const newNameStack = getNewStack(copiedNameStack, astNode.name, root); 24 | 25 | const copiedPropertyChainStack = [...propertyChainStack]; 26 | const newPropertyChainStack = getNewStack(copiedPropertyChainStack, astNode.name, root); 27 | 28 | switch (astNode.name) { 29 | case 'arrayElement': { 30 | if (!astNode.arguments) { 31 | // This arrayElement is edge node 32 | // Need to check type 33 | break; 34 | } 35 | if (astNode.type === 'array' || astNode.type === 'union') { 36 | // need more recursive call 37 | break; 38 | } 39 | return astNode.arguments 40 | .map((node) => { 41 | return generateBodyCode({ 42 | astNode: node, 43 | namePrefix, 44 | nameStack: copiedNameStack, 45 | propertyChainStack: copiedPropertyChainStack, 46 | }); 47 | }) 48 | .join('\n'); 49 | } 50 | } 51 | 52 | switch (astNode.type) { 53 | // Enum is considered as union 54 | case 'enum': 55 | case 'union': { 56 | const conditions = getConditions({ 57 | astNode, 58 | nameStack: newNameStack, 59 | namePrefix, 60 | propertyChainStack: newPropertyChainStack, 61 | }); 62 | 63 | const childrenTypes = 64 | astNode.arguments 65 | ?.map((node) => { 66 | if ( 67 | !(astNode.type.includes('"') || astNode.type.includes("'")) && 68 | astNode.type === 'object' 69 | ) { 70 | return node.name; 71 | } 72 | return node.type; 73 | }) 74 | .join(' | ') ?? ''; 75 | 76 | return [ 77 | `if (${conditions.join(' &&\n')}) {`, 78 | ` error.push({`, 79 | ` propertyName: '${astNode.name}',`, 80 | ` propertyChainTrace: [${wrapQuoteSymbol(copiedPropertyChainStack)}],`, 81 | ` expectedType: ${wrapQuoteSymbol(childrenTypes)},`, 82 | ` received: ${getName(newNameStack, namePrefix)},`, 83 | ` });`, 84 | `}`, 85 | ].join('\n'); 86 | } 87 | case 'array': { 88 | const statement = astNode.arguments?.map((arrayElement) => { 89 | return generateBodyCode({ 90 | astNode: arrayElement, 91 | namePrefix: 'elem', 92 | nameStack: [], 93 | propertyChainStack: newPropertyChainStack, 94 | }); 95 | }); 96 | 97 | // Check array elems one by one and push to error when error is occurred. 98 | // But only one error object should be in the error array. 99 | return [ 100 | `if (!Array.isArray(${getName(newNameStack, namePrefix)})) {`, 101 | ` error.push({`, 102 | ` propertyName: '${astNode.name}',`, 103 | ` propertyChainTrace: [${wrapQuoteSymbol(copiedPropertyChainStack)}],`, 104 | ` expectedType: 'array',`, 105 | ` received: ${getName(copiedNameStack, namePrefix)},`, 106 | ` });`, 107 | `} else {`, 108 | ` ${getName(newNameStack, namePrefix)}.find((elem) => {`, 109 | ` const prevErrorLen = error.length;`, 110 | ` ${statement}`, 111 | ` return prevErrorLen !== error.length;`, 112 | ` });`, 113 | `}`, 114 | ].join('\n'); 115 | } 116 | case 'tuple': { 117 | // [condition, propertyName, expectedType] 118 | const conditions: [string, string, string][] = 119 | astNode.arguments?.map((tupleElement, index) => { 120 | const condition = getConditions({ 121 | astNode: tupleElement, 122 | nameStack: [...copiedNameStack, astNode.name, `${index}`], 123 | propertyChainStack: [ 124 | ...copiedPropertyChainStack, 125 | `${astNode.name}[${index}]`, 126 | ], 127 | }); 128 | return [ 129 | condition.join(' &&\n'), 130 | `${astNode.name}[${index}]`, 131 | tupleElement.type, 132 | ]; 133 | }) ?? []; 134 | 135 | return [ 136 | `if (!Array.isArray(${getName(newNameStack, namePrefix)})) {`, 137 | ` error.push({`, 138 | ` propertyName: '${astNode.name}',`, 139 | ` propertyChainTrace: [${wrapQuoteSymbol(copiedPropertyChainStack)}],`, 140 | ` expectedType: 'tuple',`, 141 | ` received: ${getName(copiedNameStack, namePrefix)},`, 142 | ` });`, 143 | `} else {`, 144 | ` ${conditions 145 | .map(([condition, propertyName, expectedType]) => { 146 | return [ 147 | `if (${condition}) {`, 148 | ` error.push({`, 149 | ` propertyName: '${propertyName}',`, 150 | ` propertyChainTrace: [${wrapQuoteSymbol( 151 | copiedPropertyChainStack, 152 | )}],`, 153 | ` expectedType: '${expectedType}',`, 154 | ` received: ${getName(newNameStack, namePrefix)},`, 155 | ` });`, 156 | `}`, 157 | ].join('\n'); 158 | }) 159 | .join('\n')}`, 160 | `}`, 161 | ].join('\n'); 162 | } 163 | case 'class': { 164 | // class condition has only one condition 165 | const [classCondition] = getConditions({ 166 | astNode, 167 | nameStack: newNameStack, 168 | namePrefix, 169 | propertyChainStack: newPropertyChainStack, 170 | }); 171 | 172 | return [ 173 | `if (${classCondition}) {`, 174 | ` error.push({`, 175 | ` propertyName: '${astNode.name}',`, 176 | ` propertyChainTrace: [${wrapQuoteSymbol(copiedPropertyChainStack)}],`, 177 | ` expectedType: '${astNode.name}',`, 178 | ` received: ${getName(copiedNameStack, namePrefix)},`, 179 | ` });`, 180 | `}`, 181 | ].join('\n'); 182 | } 183 | // TODO: remove duplicated property error in intersection 184 | case 'intersection': 185 | case 'object': { 186 | const result = 187 | astNode.arguments?.map((node) => 188 | generateBodyCode({ 189 | astNode: node, 190 | namePrefix, 191 | nameStack: newNameStack, 192 | propertyChainStack: newPropertyChainStack, 193 | }), 194 | ) ?? []; 195 | 196 | const objectCondition = [ 197 | `if (${getName(copiedNameStack, namePrefix)} == null ||`, 198 | `(typeof ${getName(copiedNameStack, namePrefix)} !== "object" &&`, 199 | `typeof ${getName(copiedNameStack, namePrefix)} !== "function")) {`, 200 | ` error.push({`, 201 | ` propertyName: '${astNode.name}',`, 202 | ` propertyChainTrace: [${wrapQuoteSymbol(copiedPropertyChainStack)}],`, 203 | ` expectedType: 'object',`, 204 | ` received: ${getName(copiedNameStack, namePrefix)},`, 205 | ` });`, 206 | `} else {`, 207 | ` ${result.join('\n')}`, 208 | `}`, 209 | ].join('\n'); 210 | 211 | return objectCondition; 212 | } 213 | default: { 214 | return [ 215 | `if (${getConditionStatement({ 216 | astNode, 217 | nameStack: newNameStack, 218 | namePrefix, 219 | })}) {`, 220 | ` error.push({`, 221 | ` propertyName: '${astNode.name}',`, 222 | ` propertyChainTrace: [${wrapQuoteSymbol(copiedPropertyChainStack)}],`, 223 | ` expectedType: '${astNode.type}',`, 224 | ` received: ${getName(newNameStack, namePrefix)},`, 225 | ` });`, 226 | `}`, 227 | ].join('\n'); 228 | } 229 | } 230 | }; 231 | 232 | const getConditions = ({ 233 | astNode, 234 | namePrefix, 235 | nameStack, 236 | propertyChainStack, 237 | }: GenerateBodyCode): string[] => { 238 | const copiedNameStack = [...nameStack]; 239 | const copiedPropertyChainStack = [...propertyChainStack]; 240 | 241 | switch (astNode.type) { 242 | // Enum is considered as union 243 | case 'enum': 244 | case 'union': { 245 | const unionElements = astNode.arguments; 246 | if (!unionElements) throw new Error('There is no union elements'); 247 | 248 | const objectElements = unionElements.filter((unionElem) => unionElem.type === 'object'); 249 | const nonObjectElements = unionElements.filter( 250 | (unionElem) => unionElem.type !== 'object', 251 | ); 252 | 253 | const objectConditions = objectElements.flatMap((objectElem) => { 254 | return getConditions({ 255 | astNode: objectElem, 256 | namePrefix, 257 | nameStack: copiedNameStack, 258 | propertyChainStack: copiedPropertyChainStack, 259 | }); 260 | }); 261 | const objectCondition = [ 262 | `(() => {`, 263 | ` const error: GeneratedWrongTypeErrorReport = [];`, 264 | ` let errorCnt = 0;`, 265 | ` ${objectConditions 266 | .map((objCondition) => { 267 | return [`if (${objCondition}) {`, ` errorCnt++;`, `}`].join('\n'); 268 | }) 269 | .join(';\n')}`, 270 | ` return errorCnt === ${objectConditions.length};`, 271 | `})()`, 272 | ].join('\n'); 273 | 274 | const unionConditions = nonObjectElements.flatMap((unionElemNode) => { 275 | return getConditions({ 276 | astNode: unionElemNode, 277 | namePrefix, 278 | nameStack: copiedNameStack, 279 | propertyChainStack: copiedPropertyChainStack, 280 | }); 281 | }); 282 | if (objectConditions.length > 0) { 283 | unionConditions.unshift(objectCondition); 284 | } 285 | 286 | return unionConditions; 287 | } 288 | case 'class': { 289 | return [ 290 | `!(${getName(nameStack, namePrefix)} instanceof ${astNode.arguments![0].type})`, 291 | ]; 292 | } 293 | case 'tuple': 294 | case 'array': 295 | case 'intersection': 296 | case 'object': { 297 | // TODO: optimization 298 | const statement = generateBodyCode({ 299 | astNode, 300 | namePrefix, 301 | nameStack: copiedNameStack, 302 | propertyChainStack: copiedPropertyChainStack, 303 | }); 304 | return [ 305 | [ 306 | `(() => {`, 307 | ` const prevErrorLen = error.length;`, 308 | ` ${statement}`, 309 | ` return prevErrorLen !== error.length;`, 310 | `})()`, 311 | ].join('\n'), 312 | ]; 313 | } 314 | default: { 315 | return [ 316 | getConditionStatement({ 317 | astNode, 318 | nameStack, 319 | namePrefix, 320 | }), 321 | ]; 322 | } 323 | } 324 | }; 325 | 326 | const getConditionStatement = ({ 327 | astNode, 328 | nameStack, 329 | namePrefix, 330 | }: { 331 | astNode: AstNode; 332 | nameStack: string[]; 333 | namePrefix?: string; 334 | }) => { 335 | if ( 336 | // imported value should not be used with Quote symbols (''). 337 | // Enum uses imported values. 338 | astNode.name === 'enumElement' || 339 | astNode.type === 'undefined' || 340 | astNode.type === 'null' 341 | ) { 342 | return `${getName(nameStack, namePrefix)} !== ${astNode.type}`; 343 | } 344 | 345 | const primitives = ['string', 'number', 'bigint', 'boolean', 'symbol', 'undefined', 'null']; 346 | if (primitives.includes(astNode.type)) { 347 | return `typeof ${getName(nameStack, namePrefix)} !== '${astNode.type}'`; 348 | } 349 | 350 | return `${getName(nameStack, namePrefix)} !== ${astNode.type}`; 351 | }; 352 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/importTestCase.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getSourceFile, 3 | getTestCase, 4 | getTypeDeclaration, 5 | } from '../../../tests/utils'; 6 | import { AstRootNode } from '../../reporterAst'; 7 | import { getNewRootAst, getNewAstNode } from '../../reporterAst/astUtils'; 8 | import { codeGenerator } from '../codeGenerator'; 9 | 10 | describe('transformer - imports', () => { 11 | const { typeDeclarations, project } = getTestCase('import/importTestCase'); 12 | 13 | test('Enum should be imported properly', async () => { 14 | // Given 15 | const sourceFile = getSourceFile('import/toBeImported', project); 16 | const astRootNode = getNewRootAst({ 17 | astNode: getNewAstNode({ 18 | name: 'Test', 19 | type: 'object', 20 | argument: [ 21 | getNewAstNode({ 22 | name: 'type', 23 | type: 'union', 24 | argument: [ 25 | getNewAstNode({ 26 | name: 'enumElement', 27 | type: 'TestType.t', 28 | }), 29 | getNewAstNode({ 30 | name: 'enumElement', 31 | type: 'TestType.a', 32 | }), 33 | getNewAstNode({ 34 | name: 'enumElement', 35 | type: 'TestType.b', 36 | }), 37 | ], 38 | }), 39 | ], 40 | }), 41 | dependencies: new Map([[sourceFile, { TestType: 'TestType' }]]), 42 | }); 43 | // When 44 | await codeGenerator({ 45 | astRootNode, 46 | project, 47 | outFilePath: 'src/codeGenerator/tests/asdf.ts', 48 | inputSourceFile: sourceFile, 49 | }); 50 | 51 | // Then 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/miniTest.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getTestCase, 3 | getTypeDeclaration, 4 | getSourceFile, 5 | } from '../../../tests/utils'; 6 | import { transformer } from '../../transformer/transformer'; 7 | import { codeGenerator } from '../codeGenerator'; 8 | 9 | describe('mini test', () => { 10 | const { typeDeclarations, project } = getTestCase('mini/miniTest'); 11 | 12 | test('OneObject', async () => { 13 | // Given 14 | const typeDeclaration = getTypeDeclaration( 15 | 'OneObject', 16 | typeDeclarations, 17 | ); 18 | const newAst = transformer(typeDeclaration); 19 | 20 | // When 21 | await codeGenerator({ 22 | astRootNode: newAst, 23 | project, 24 | outFilePath: 'src/codeGenerator/tests/miniTestResults/OneObject.ts', 25 | inputSourceFile: getSourceFile('import/toBeImported', project), 26 | }); 27 | 28 | // Then 29 | }); 30 | 31 | test('WithGenerics', async () => { 32 | // Given 33 | const typeDeclaration = getTypeDeclaration( 34 | 'WithGenerics', 35 | typeDeclarations, 36 | ); 37 | const newAst = transformer(typeDeclaration); 38 | 39 | // When 40 | await codeGenerator({ 41 | astRootNode: newAst, 42 | project, 43 | outFilePath: 44 | 'src/codeGenerator/tests/miniTestResults/WithGenerics.ts', 45 | inputSourceFile: getSourceFile('import/toBeImported', project), 46 | }); 47 | 48 | // Then 49 | }); 50 | 51 | test('Enums', async () => { 52 | // Given 53 | const typeDeclaration = getTypeDeclaration('Enums', typeDeclarations); 54 | const newAst = transformer(typeDeclaration); 55 | 56 | // When 57 | await codeGenerator({ 58 | astRootNode: newAst, 59 | project, 60 | outFilePath: 'src/codeGenerator/tests/miniTestResults/Enums.ts', 61 | inputSourceFile: getSourceFile('import/toBeImported', project), 62 | }); 63 | 64 | // Then 65 | }); 66 | 67 | test('NameSpace', async () => { 68 | // Given 69 | const typeDeclaration = getTypeDeclaration( 70 | 'NameSpace', 71 | typeDeclarations, 72 | ); 73 | const newAst = transformer(typeDeclaration); 74 | 75 | // When 76 | await codeGenerator({ 77 | astRootNode: newAst, 78 | project, 79 | outFilePath: 'src/codeGenerator/tests/miniTestResults/NameSpace.ts', 80 | inputSourceFile: getSourceFile('import/toBeImported', project), 81 | }); 82 | 83 | // Then 84 | }); 85 | 86 | test('PrimitiveOneArray', async () => { 87 | // Given 88 | const typeDeclaration = getTypeDeclaration( 89 | 'PrimitiveOneArray', 90 | typeDeclarations, 91 | ); 92 | const newAst = transformer(typeDeclaration); 93 | 94 | // When 95 | await codeGenerator({ 96 | astRootNode: newAst, 97 | project, 98 | outFilePath: 99 | 'src/codeGenerator/tests/miniTestResults/PrimitiveOneArray.ts', 100 | inputSourceFile: getSourceFile('import/toBeImported', project), 101 | }); 102 | 103 | // Then 104 | }); 105 | 106 | test('PrimitiveArrays', async () => { 107 | // Given 108 | const typeDeclaration = getTypeDeclaration( 109 | 'PrimitiveArrays', 110 | typeDeclarations, 111 | ); 112 | const newAst = transformer(typeDeclaration); 113 | 114 | // When 115 | await codeGenerator({ 116 | astRootNode: newAst, 117 | project, 118 | outFilePath: 119 | 'src/codeGenerator/tests/miniTestResults/PrimitiveArrays.ts', 120 | inputSourceFile: getSourceFile('import/toBeImported', project), 121 | }); 122 | 123 | // Then 124 | }); 125 | 126 | test('PrimitiveOneArrayWithObject', async () => { 127 | // Given 128 | const typeDeclaration = getTypeDeclaration( 129 | 'PrimitiveOneArrayWithObject', 130 | typeDeclarations, 131 | ); 132 | const newAst = transformer(typeDeclaration); 133 | 134 | // When 135 | await codeGenerator({ 136 | astRootNode: newAst, 137 | project, 138 | outFilePath: 139 | 'src/codeGenerator/tests/miniTestResults/PrimitiveOneArrayWithObject.ts', 140 | inputSourceFile: getSourceFile('import/toBeImported', project), 141 | }); 142 | 143 | // Then 144 | }); 145 | 146 | test('PrimitiveArraysWithObject', async () => { 147 | // Given 148 | const typeDeclaration = getTypeDeclaration( 149 | 'PrimitiveArraysWithObject', 150 | typeDeclarations, 151 | ); 152 | const newAst = transformer(typeDeclaration); 153 | 154 | // When 155 | await codeGenerator({ 156 | astRootNode: newAst, 157 | project, 158 | outFilePath: 159 | 'src/codeGenerator/tests/miniTestResults/PrimitiveArraysWithObject.ts', 160 | inputSourceFile: getSourceFile('import/toBeImported', project), 161 | }); 162 | 163 | // Then 164 | }); 165 | 166 | test('OneArray', async () => { 167 | // Given 168 | const typeDeclaration = getTypeDeclaration( 169 | 'OneArray', 170 | typeDeclarations, 171 | ); 172 | const newAst = transformer(typeDeclaration); 173 | 174 | // When 175 | await codeGenerator({ 176 | astRootNode: newAst, 177 | project, 178 | outFilePath: 'src/codeGenerator/tests/miniTestResults/OneArray.ts', 179 | inputSourceFile: getSourceFile('import/toBeImported', project), 180 | }); 181 | 182 | // Then 183 | }); 184 | 185 | test('Arrays', async () => { 186 | // Given 187 | const typeDeclaration = getTypeDeclaration('Arrays', typeDeclarations); 188 | const newAst = transformer(typeDeclaration); 189 | 190 | // When 191 | await codeGenerator({ 192 | astRootNode: newAst, 193 | project, 194 | outFilePath: 'src/codeGenerator/tests/miniTestResults/Arrays.ts', 195 | inputSourceFile: getSourceFile('import/toBeImported', project), 196 | }); 197 | 198 | // Then 199 | }); 200 | 201 | test('OneArrayWithObject', async () => { 202 | // Given 203 | const typeDeclaration = getTypeDeclaration( 204 | 'OneArrayWithObject', 205 | typeDeclarations, 206 | ); 207 | const newAst = transformer(typeDeclaration); 208 | 209 | // When 210 | await codeGenerator({ 211 | astRootNode: newAst, 212 | project, 213 | outFilePath: 214 | 'src/codeGenerator/tests/miniTestResults/OneArrayWithObject.ts', 215 | inputSourceFile: getSourceFile('import/toBeImported', project), 216 | }); 217 | 218 | // Then 219 | }); 220 | 221 | test('ArraysWithObject', async () => { 222 | // Given 223 | const typeDeclaration = getTypeDeclaration( 224 | 'ArraysWithObject', 225 | typeDeclarations, 226 | ); 227 | const newAst = transformer(typeDeclaration); 228 | 229 | // When 230 | await codeGenerator({ 231 | astRootNode: newAst, 232 | project, 233 | outFilePath: 234 | 'src/codeGenerator/tests/miniTestResults/ArraysWithObject.ts', 235 | inputSourceFile: getSourceFile('import/toBeImported', project), 236 | }); 237 | 238 | // Then 239 | }); 240 | 241 | test('PrimitiveUnion', async () => { 242 | // Given 243 | const typeDeclaration = getTypeDeclaration( 244 | 'PrimitiveUnion', 245 | typeDeclarations, 246 | ); 247 | const newAst = transformer(typeDeclaration); 248 | 249 | // When 250 | await codeGenerator({ 251 | astRootNode: newAst, 252 | project, 253 | outFilePath: 254 | 'src/codeGenerator/tests/miniTestResults/PrimitiveUnion.ts', 255 | inputSourceFile: getSourceFile('import/toBeImported', project), 256 | }); 257 | 258 | // Then 259 | }); 260 | 261 | test('PrimitiveUnionWithObject', async () => { 262 | // Given 263 | const typeDeclaration = getTypeDeclaration( 264 | 'PrimitiveUnionWithObject', 265 | typeDeclarations, 266 | ); 267 | const newAst = transformer(typeDeclaration); 268 | 269 | // When 270 | await codeGenerator({ 271 | astRootNode: newAst, 272 | project, 273 | outFilePath: 274 | 'src/codeGenerator/tests/miniTestResults/PrimitiveUnionWithObject.ts', 275 | inputSourceFile: getSourceFile('import/toBeImported', project), 276 | }); 277 | 278 | // Then 279 | }); 280 | 281 | test('Union', async () => { 282 | // Given 283 | const typeDeclaration = getTypeDeclaration('Union', typeDeclarations); 284 | const newAst = transformer(typeDeclaration); 285 | 286 | // When 287 | await codeGenerator({ 288 | astRootNode: newAst, 289 | project, 290 | outFilePath: 'src/codeGenerator/tests/miniTestResults/Union.ts', 291 | inputSourceFile: getSourceFile('import/toBeImported', project), 292 | }); 293 | 294 | // Then 295 | }); 296 | 297 | test('UnionWithObject', async () => { 298 | // Given 299 | const typeDeclaration = getTypeDeclaration( 300 | 'UnionWithObject', 301 | typeDeclarations, 302 | ); 303 | const newAst = transformer(typeDeclaration); 304 | 305 | // When 306 | await codeGenerator({ 307 | astRootNode: newAst, 308 | project, 309 | outFilePath: 310 | 'src/codeGenerator/tests/miniTestResults/UnionWithObject.ts', 311 | inputSourceFile: getSourceFile('import/toBeImported', project), 312 | }); 313 | 314 | // Then 315 | }); 316 | 317 | test('PrimitiveIntersection', async () => { 318 | // Given 319 | const typeDeclaration = getTypeDeclaration( 320 | 'PrimitiveIntersection', 321 | typeDeclarations, 322 | ); 323 | const newAst = transformer(typeDeclaration); 324 | 325 | // When 326 | await codeGenerator({ 327 | astRootNode: newAst, 328 | project, 329 | outFilePath: 330 | 'src/codeGenerator/tests/miniTestResults/PrimitiveIntersection.ts', 331 | inputSourceFile: getSourceFile('import/toBeImported', project), 332 | }); 333 | 334 | // Then 335 | }); 336 | 337 | test('Intersection', async () => { 338 | // Given 339 | const typeDeclaration = getTypeDeclaration( 340 | 'Intersection', 341 | typeDeclarations, 342 | ); 343 | const newAst = transformer(typeDeclaration); 344 | 345 | // When 346 | await codeGenerator({ 347 | astRootNode: newAst, 348 | project, 349 | outFilePath: 350 | 'src/codeGenerator/tests/miniTestResults/Intersection.ts', 351 | inputSourceFile: getSourceFile('import/toBeImported', project), 352 | }); 353 | 354 | // Then 355 | }); 356 | 357 | test('Tuple', async () => { 358 | // Given 359 | const typeDeclaration = getTypeDeclaration('Tuple', typeDeclarations); 360 | const newAst = transformer(typeDeclaration); 361 | 362 | // When 363 | await codeGenerator({ 364 | astRootNode: newAst, 365 | project, 366 | outFilePath: 'src/codeGenerator/tests/miniTestResults/Tuple.ts', 367 | inputSourceFile: getSourceFile('import/toBeImported', project), 368 | }); 369 | 370 | // Then 371 | }); 372 | }); 373 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/miniTestResults/Arrays.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { Arrays } from "../../../../tests/cases/mini/miniTest"; 3 | import { GeneratedWrongTypeErrorReport } from "../../../wrongTypeReportGenerator"; 4 | 5 | export const validateArrays = (value: unknown): GeneratedWrongTypeErrorReport | undefined => { 6 | const typedValue = value as Arrays; 7 | const error: GeneratedWrongTypeErrorReport = []; 8 | if (typedValue == null || 9 | (typeof typedValue !== "object" && 10 | typeof typedValue !== "function")) { 11 | error.push({ 12 | propertyName: 'Arrays', 13 | propertyChainTrace: [], 14 | expectedType: 'object', 15 | received: typedValue, 16 | }); 17 | } else { 18 | if (!Array.isArray(typedValue['t1'])) { 19 | error.push({ 20 | propertyName: 't1', 21 | propertyChainTrace: [], 22 | expectedType: 'array', 23 | received: typedValue, 24 | }); 25 | } else { 26 | typedValue['t1'].find((elem) => { 27 | const prevErrorLen = error.length; 28 | if (!Array.isArray(elem)) { 29 | error.push({ 30 | propertyName: 'arrayElement', 31 | propertyChainTrace: ['t1'], 32 | expectedType: 'array', 33 | received: elem, 34 | }); 35 | } else { 36 | elem.find((elem) => { 37 | const prevErrorLen = error.length; 38 | if (typeof elem !== 'number') { 39 | error.push({ 40 | propertyName: 'arrayElement', 41 | propertyChainTrace: ['t1'], 42 | expectedType: 'number', 43 | received: elem, 44 | }); 45 | } 46 | return prevErrorLen !== error.length; 47 | }); 48 | } 49 | return prevErrorLen !== error.length; 50 | }); 51 | } 52 | if (!Array.isArray(typedValue['t2'])) { 53 | error.push({ 54 | propertyName: 't2', 55 | propertyChainTrace: [], 56 | expectedType: 'array', 57 | received: typedValue, 58 | }); 59 | } else { 60 | typedValue['t2'].find((elem) => { 61 | const prevErrorLen = error.length; 62 | if (!Array.isArray(elem)) { 63 | error.push({ 64 | propertyName: 'arrayElement', 65 | propertyChainTrace: ['t2'], 66 | expectedType: 'array', 67 | received: elem, 68 | }); 69 | } else { 70 | elem.find((elem) => { 71 | const prevErrorLen = error.length; 72 | if (!Array.isArray(elem)) { 73 | error.push({ 74 | propertyName: 'arrayElement', 75 | propertyChainTrace: ['t2'], 76 | expectedType: 'array', 77 | received: elem, 78 | }); 79 | } else { 80 | elem.find((elem) => { 81 | const prevErrorLen = error.length; 82 | if (typeof elem !== 'number') { 83 | error.push({ 84 | propertyName: 'arrayElement', 85 | propertyChainTrace: ['t2'], 86 | expectedType: 'number', 87 | received: elem, 88 | }); 89 | } 90 | return prevErrorLen !== error.length; 91 | }); 92 | } 93 | return prevErrorLen !== error.length; 94 | }); 95 | } 96 | return prevErrorLen !== error.length; 97 | }); 98 | } 99 | } 100 | return error.length === 0 ? undefined : error; 101 | } 102 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/miniTestResults/ArraysWithObject.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { ArraysWithObject } from "../../../../tests/cases/mini/miniTest"; 3 | import { GeneratedWrongTypeErrorReport } from "../../../wrongTypeReportGenerator"; 4 | 5 | export const validateArraysWithObject = (value: unknown): GeneratedWrongTypeErrorReport | undefined => { 6 | const typedValue = value as ArraysWithObject; 7 | const error: GeneratedWrongTypeErrorReport = []; 8 | if (typedValue == null || 9 | (typeof typedValue !== "object" && 10 | typeof typedValue !== "function")) { 11 | error.push({ 12 | propertyName: 'ArraysWithObject', 13 | propertyChainTrace: [], 14 | expectedType: 'object', 15 | received: typedValue, 16 | }); 17 | } else { 18 | if (!Array.isArray(typedValue['t1'])) { 19 | error.push({ 20 | propertyName: 't1', 21 | propertyChainTrace: [], 22 | expectedType: 'array', 23 | received: typedValue, 24 | }); 25 | } else { 26 | typedValue['t1'].find((elem) => { 27 | const prevErrorLen = error.length; 28 | if (!Array.isArray(elem)) { 29 | error.push({ 30 | propertyName: 'arrayElement', 31 | propertyChainTrace: ['t1'], 32 | expectedType: 'array', 33 | received: elem, 34 | }); 35 | } else { 36 | elem.find((elem) => { 37 | const prevErrorLen = error.length; 38 | if (typeof elem['t2'] !== 'number') { 39 | error.push({ 40 | propertyName: 't2', 41 | propertyChainTrace: ['t1'], 42 | expectedType: 'number', 43 | received: elem['t2'], 44 | }); 45 | } 46 | return prevErrorLen !== error.length; 47 | }); 48 | } 49 | return prevErrorLen !== error.length; 50 | }); 51 | } 52 | } 53 | return error.length === 0 ? undefined : error; 54 | } 55 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/miniTestResults/Enums.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { Enums } from "../../../../tests/cases/mini/miniTest"; 3 | import { GeneratedWrongTypeErrorReport } from "../../../wrongTypeReportGenerator"; 4 | export const validateEnums = (value: unknown): GeneratedWrongTypeErrorReport | undefined => { 5 | const typedValue = value as Enums; 6 | const error: GeneratedWrongTypeErrorReport = []; 7 | if (typedValue !== Enums.test1 && 8 | typedValue !== Enums.test2) { 9 | error.push({ 10 | propertyName: 'Enums', 11 | propertyChainTrace: [], 12 | expectedType: 'Enums.test1 | Enums.test2', 13 | received: typedValue, 14 | }); 15 | } 16 | return error.length === 0 ? undefined : error; 17 | } 18 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/miniTestResults/Intersection.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { Intersection } from "../../../../tests/cases/mini/miniTest"; 3 | import { GeneratedWrongTypeErrorReport } from "../../../wrongTypeReportGenerator"; 4 | 5 | export const validateIntersection = (value: unknown): GeneratedWrongTypeErrorReport | undefined => { 6 | const typedValue = value as Intersection; 7 | const error: GeneratedWrongTypeErrorReport = []; 8 | if (typedValue == null || 9 | (typeof typedValue !== "object" && 10 | typeof typedValue !== "function")) { 11 | error.push({ 12 | propertyName: 'Intersection', 13 | propertyChainTrace: [], 14 | expectedType: 'object', 15 | received: typedValue, 16 | }); 17 | } else { 18 | if (typedValue == null || 19 | (typeof typedValue !== "object" && 20 | typeof typedValue !== "function")) { 21 | error.push({ 22 | propertyName: 't1', 23 | propertyChainTrace: [], 24 | expectedType: 'object', 25 | received: typedValue, 26 | }); 27 | } else { 28 | if (typedValue['t1'] == null || 29 | (typeof typedValue['t1'] !== "object" && 30 | typeof typedValue['t1'] !== "function")) { 31 | error.push({ 32 | propertyName: 'intersectionElement', 33 | propertyChainTrace: ['t1'], 34 | expectedType: 'object', 35 | received: typedValue['t1'], 36 | }); 37 | } else { 38 | if (typeof typedValue['t1']['t2'] !== 'string') { 39 | error.push({ 40 | propertyName: 't2', 41 | propertyChainTrace: ['t1'], 42 | expectedType: 'string', 43 | received: typedValue['t1']['t2'], 44 | }); 45 | } 46 | } 47 | if (typedValue['t1'] == null || 48 | (typeof typedValue['t1'] !== "object" && 49 | typeof typedValue['t1'] !== "function")) { 50 | error.push({ 51 | propertyName: 'intersectionElement', 52 | propertyChainTrace: ['t1'], 53 | expectedType: 'object', 54 | received: typedValue['t1'], 55 | }); 56 | } else { 57 | if (typeof typedValue['t1']['t3'] !== 'number') { 58 | error.push({ 59 | propertyName: 't3', 60 | propertyChainTrace: ['t1'], 61 | expectedType: 'number', 62 | received: typedValue['t1']['t3'], 63 | }); 64 | } 65 | } 66 | } 67 | } 68 | return error.length === 0 ? undefined : error; 69 | } 70 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/miniTestResults/NameSpace.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { NameSpace } from "../../../../tests/cases/mini/miniTest"; 3 | import { GeneratedWrongTypeErrorReport } from "../../../wrongTypeReportGenerator"; 4 | 5 | export const validateNameSpace = (value: unknown): GeneratedWrongTypeErrorReport | undefined => { 6 | const typedValue = value as NameSpace; 7 | const error: GeneratedWrongTypeErrorReport = []; 8 | if (typedValue == null || 9 | (typeof typedValue !== "object" && 10 | typeof typedValue !== "function")) { 11 | error.push({ 12 | propertyName: 'NameSpace', 13 | propertyChainTrace: [], 14 | expectedType: 'object', 15 | received: typedValue, 16 | }); 17 | } else { 18 | if (typedValue == null || 19 | (typeof typedValue !== "object" && 20 | typeof typedValue !== "function")) { 21 | error.push({ 22 | propertyName: 't1', 23 | propertyChainTrace: [], 24 | expectedType: 'object', 25 | received: typedValue, 26 | }); 27 | } else { 28 | if (typeof typedValue['t1']['t1'] !== 'number') { 29 | error.push({ 30 | propertyName: 't1', 31 | propertyChainTrace: ['t1'], 32 | expectedType: 'number', 33 | received: typedValue['t1']['t1'], 34 | }); 35 | } 36 | } 37 | } 38 | return error.length === 0 ? undefined : error; 39 | } 40 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/miniTestResults/OneArray.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { OneArray } from "../../../../tests/cases/mini/miniTest"; 3 | import { GeneratedWrongTypeErrorReport } from "../../../wrongTypeReportGenerator"; 4 | 5 | export const validateOneArray = (value: unknown): GeneratedWrongTypeErrorReport | undefined => { 6 | const typedValue = value as OneArray; 7 | const error: GeneratedWrongTypeErrorReport = []; 8 | if (typedValue == null || 9 | (typeof typedValue !== "object" && 10 | typeof typedValue !== "function")) { 11 | error.push({ 12 | propertyName: 'OneArray', 13 | propertyChainTrace: [], 14 | expectedType: 'object', 15 | received: typedValue, 16 | }); 17 | } else { 18 | if (!Array.isArray(typedValue['t1'])) { 19 | error.push({ 20 | propertyName: 't1', 21 | propertyChainTrace: [], 22 | expectedType: 'array', 23 | received: typedValue, 24 | }); 25 | } else { 26 | typedValue['t1'].find((elem) => { 27 | const prevErrorLen = error.length; 28 | if (typeof elem !== 'number') { 29 | error.push({ 30 | propertyName: 'arrayElement', 31 | propertyChainTrace: ['t1'], 32 | expectedType: 'number', 33 | received: elem, 34 | }); 35 | } 36 | return prevErrorLen !== error.length; 37 | }); 38 | } 39 | } 40 | return error.length === 0 ? undefined : error; 41 | } 42 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/miniTestResults/OneArrayWithObject.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { OneArrayWithObject } from "../../../../tests/cases/mini/miniTest"; 3 | import { GeneratedWrongTypeErrorReport } from "../../../wrongTypeReportGenerator"; 4 | 5 | export const validateOneArrayWithObject = (value: unknown): GeneratedWrongTypeErrorReport | undefined => { 6 | const typedValue = value as OneArrayWithObject; 7 | const error: GeneratedWrongTypeErrorReport = []; 8 | if (typedValue == null || 9 | (typeof typedValue !== "object" && 10 | typeof typedValue !== "function")) { 11 | error.push({ 12 | propertyName: 'OneArrayWithObject', 13 | propertyChainTrace: [], 14 | expectedType: 'object', 15 | received: typedValue, 16 | }); 17 | } else { 18 | if (!Array.isArray(typedValue['t1'])) { 19 | error.push({ 20 | propertyName: 't1', 21 | propertyChainTrace: [], 22 | expectedType: 'array', 23 | received: typedValue, 24 | }); 25 | } else { 26 | typedValue['t1'].find((elem) => { 27 | const prevErrorLen = error.length; 28 | if (typeof elem['t2'] !== 'number') { 29 | error.push({ 30 | propertyName: 't2', 31 | propertyChainTrace: ['t1'], 32 | expectedType: 'number', 33 | received: elem['t2'], 34 | }); 35 | } 36 | return prevErrorLen !== error.length; 37 | }); 38 | } 39 | } 40 | return error.length === 0 ? undefined : error; 41 | } 42 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/miniTestResults/OneObject.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { OneObject } from "../../../../tests/cases/mini/miniTest"; 3 | import { GeneratedWrongTypeErrorReport } from "../../../wrongTypeReportGenerator"; 4 | 5 | export const validateOneObject = (value: unknown): GeneratedWrongTypeErrorReport | undefined => { 6 | const typedValue = value as OneObject; 7 | const error: GeneratedWrongTypeErrorReport = []; 8 | if (typedValue == null || 9 | (typeof typedValue !== "object" && 10 | typeof typedValue !== "function")) { 11 | error.push({ 12 | propertyName: 'OneObject', 13 | propertyChainTrace: [], 14 | expectedType: 'object', 15 | received: typedValue, 16 | }); 17 | } else { 18 | if (typeof typedValue['t1'] !== 'number') { 19 | error.push({ 20 | propertyName: 't1', 21 | propertyChainTrace: [], 22 | expectedType: 'number', 23 | received: typedValue['t1'], 24 | }); 25 | } 26 | } 27 | return error.length === 0 ? undefined : error; 28 | } 29 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/miniTestResults/PrimitiveArrays.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { PrimitiveArrays } from "../../../../tests/cases/mini/miniTest"; 3 | import { GeneratedWrongTypeErrorReport } from "../../../wrongTypeReportGenerator"; 4 | 5 | export const validatePrimitiveArrays = (value: unknown): GeneratedWrongTypeErrorReport | undefined => { 6 | const typedValue = value as PrimitiveArrays; 7 | const error: GeneratedWrongTypeErrorReport = []; 8 | if (!Array.isArray(typedValue)) { 9 | error.push({ 10 | propertyName: 'PrimitiveArrays', 11 | propertyChainTrace: [], 12 | expectedType: 'array', 13 | received: typedValue, 14 | }); 15 | } else { 16 | typedValue.find((elem) => { 17 | const prevErrorLen = error.length; 18 | if (!Array.isArray(elem)) { 19 | error.push({ 20 | propertyName: 'arrayElement', 21 | propertyChainTrace: [], 22 | expectedType: 'array', 23 | received: elem, 24 | }); 25 | } else { 26 | elem.find((elem) => { 27 | const prevErrorLen = error.length; 28 | if (typeof elem !== 'number') { 29 | error.push({ 30 | propertyName: 'arrayElement', 31 | propertyChainTrace: [], 32 | expectedType: 'number', 33 | received: elem, 34 | }); 35 | } 36 | return prevErrorLen !== error.length; 37 | }); 38 | } 39 | return prevErrorLen !== error.length; 40 | }); 41 | } 42 | return error.length === 0 ? undefined : error; 43 | } 44 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/miniTestResults/PrimitiveArraysWithObject.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { PrimitiveArraysWithObject } from "../../../../tests/cases/mini/miniTest"; 3 | import { GeneratedWrongTypeErrorReport } from "../../../wrongTypeReportGenerator"; 4 | 5 | export const validatePrimitiveArraysWithObject = (value: unknown): GeneratedWrongTypeErrorReport | undefined => { 6 | const typedValue = value as PrimitiveArraysWithObject; 7 | const error: GeneratedWrongTypeErrorReport = []; 8 | if (!Array.isArray(typedValue)) { 9 | error.push({ 10 | propertyName: 'PrimitiveArraysWithObject', 11 | propertyChainTrace: [], 12 | expectedType: 'array', 13 | received: typedValue, 14 | }); 15 | } else { 16 | typedValue.find((elem) => { 17 | const prevErrorLen = error.length; 18 | if (!Array.isArray(elem)) { 19 | error.push({ 20 | propertyName: 'arrayElement', 21 | propertyChainTrace: [], 22 | expectedType: 'array', 23 | received: elem, 24 | }); 25 | } else { 26 | elem.find((elem) => { 27 | const prevErrorLen = error.length; 28 | if (typeof elem['t1'] !== 'number') { 29 | error.push({ 30 | propertyName: 't1', 31 | propertyChainTrace: [], 32 | expectedType: 'number', 33 | received: elem['t1'], 34 | }); 35 | } 36 | return prevErrorLen !== error.length; 37 | }); 38 | } 39 | return prevErrorLen !== error.length; 40 | }); 41 | } 42 | return error.length === 0 ? undefined : error; 43 | } 44 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/miniTestResults/PrimitiveIntersection.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { PrimitiveIntersection } from "../../../../tests/cases/mini/miniTest"; 3 | import { GeneratedWrongTypeErrorReport } from "../../../wrongTypeReportGenerator"; 4 | 5 | export const validatePrimitiveIntersection = (value: unknown): GeneratedWrongTypeErrorReport | undefined => { 6 | const typedValue = value as PrimitiveIntersection; 7 | const error: GeneratedWrongTypeErrorReport = []; 8 | if (typedValue == null || 9 | (typeof typedValue !== "object" && 10 | typeof typedValue !== "function")) { 11 | error.push({ 12 | propertyName: 'PrimitiveIntersection', 13 | propertyChainTrace: [], 14 | expectedType: 'object', 15 | received: typedValue, 16 | }); 17 | } else { 18 | if (typedValue == null || 19 | (typeof typedValue !== "object" && 20 | typeof typedValue !== "function")) { 21 | error.push({ 22 | propertyName: 'intersectionElement', 23 | propertyChainTrace: [], 24 | expectedType: 'object', 25 | received: typedValue, 26 | }); 27 | } else { 28 | if (typeof typedValue['t1'] !== 'string') { 29 | error.push({ 30 | propertyName: 't1', 31 | propertyChainTrace: [], 32 | expectedType: 'string', 33 | received: typedValue['t1'], 34 | }); 35 | } 36 | } 37 | if (typedValue == null || 38 | (typeof typedValue !== "object" && 39 | typeof typedValue !== "function")) { 40 | error.push({ 41 | propertyName: 'intersectionElement', 42 | propertyChainTrace: [], 43 | expectedType: 'object', 44 | received: typedValue, 45 | }); 46 | } else { 47 | if (typeof typedValue['t2'] !== 'number') { 48 | error.push({ 49 | propertyName: 't2', 50 | propertyChainTrace: [], 51 | expectedType: 'number', 52 | received: typedValue['t2'], 53 | }); 54 | } 55 | } 56 | } 57 | return error.length === 0 ? undefined : error; 58 | } 59 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/miniTestResults/PrimitiveOneArray.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { PrimitiveOneArray } from "../../../../tests/cases/mini/miniTest"; 3 | import { GeneratedWrongTypeErrorReport } from "../../../wrongTypeReportGenerator"; 4 | 5 | export const validatePrimitiveOneArray = (value: unknown): GeneratedWrongTypeErrorReport | undefined => { 6 | const typedValue = value as PrimitiveOneArray; 7 | const error: GeneratedWrongTypeErrorReport = []; 8 | if (!Array.isArray(typedValue)) { 9 | error.push({ 10 | propertyName: 'PrimitiveOneArray', 11 | propertyChainTrace: [], 12 | expectedType: 'array', 13 | received: typedValue, 14 | }); 15 | } else { 16 | typedValue.find((elem) => { 17 | const prevErrorLen = error.length; 18 | if (typeof elem !== 'number') { 19 | error.push({ 20 | propertyName: 'arrayElement', 21 | propertyChainTrace: [], 22 | expectedType: 'number', 23 | received: elem, 24 | }); 25 | } 26 | return prevErrorLen !== error.length; 27 | }); 28 | } 29 | return error.length === 0 ? undefined : error; 30 | } 31 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/miniTestResults/PrimitiveOneArrayWithObject.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { PrimitiveOneArrayWithObject } from "../../../../tests/cases/mini/miniTest"; 3 | import { GeneratedWrongTypeErrorReport } from "../../../wrongTypeReportGenerator"; 4 | 5 | export const validatePrimitiveOneArrayWithObject = (value: unknown): GeneratedWrongTypeErrorReport | undefined => { 6 | const typedValue = value as PrimitiveOneArrayWithObject; 7 | const error: GeneratedWrongTypeErrorReport = []; 8 | if (!Array.isArray(typedValue)) { 9 | error.push({ 10 | propertyName: 'PrimitiveOneArrayWithObject', 11 | propertyChainTrace: [], 12 | expectedType: 'array', 13 | received: typedValue, 14 | }); 15 | } else { 16 | typedValue.find((elem) => { 17 | const prevErrorLen = error.length; 18 | if (typeof elem['t1'] !== 'number') { 19 | error.push({ 20 | propertyName: 't1', 21 | propertyChainTrace: [], 22 | expectedType: 'number', 23 | received: elem['t1'], 24 | }); 25 | } 26 | return prevErrorLen !== error.length; 27 | }); 28 | } 29 | return error.length === 0 ? undefined : error; 30 | } 31 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/miniTestResults/PrimitiveUnion.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { PrimitiveUnion } from "../../../../tests/cases/mini/miniTest"; 3 | import { GeneratedWrongTypeErrorReport } from "../../../wrongTypeReportGenerator"; 4 | 5 | export const validatePrimitiveUnion = (value: unknown): GeneratedWrongTypeErrorReport | undefined => { 6 | const typedValue = value as PrimitiveUnion; 7 | const error: GeneratedWrongTypeErrorReport = []; 8 | if (typeof typedValue !== 'number' && 9 | typedValue !== "asdf" && 10 | typedValue !== "qwer") { 11 | error.push({ 12 | propertyName: 'PrimitiveUnion', 13 | propertyChainTrace: [], 14 | expectedType: 'number | "asdf" | "qwer"', 15 | received: typedValue, 16 | }); 17 | } 18 | return error.length === 0 ? undefined : error; 19 | } 20 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/miniTestResults/PrimitiveUnionWithObject.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { PrimitiveUnionWithObject } from "../../../../tests/cases/mini/miniTest"; 3 | import { GeneratedWrongTypeErrorReport } from "../../../wrongTypeReportGenerator"; 4 | 5 | export const validatePrimitiveUnionWithObject = (value: unknown): GeneratedWrongTypeErrorReport | undefined => { 6 | const typedValue = value as PrimitiveUnionWithObject; 7 | const error: GeneratedWrongTypeErrorReport = []; 8 | if ((() => { 9 | const error: GeneratedWrongTypeErrorReport = []; 10 | let errorCnt = 0; 11 | if ((() => { 12 | const prevErrorLen = error.length; 13 | if (typedValue == null || 14 | (typeof typedValue !== "object" && 15 | typeof typedValue !== "function")) { 16 | error.push({ 17 | propertyName: 'unionElement', 18 | propertyChainTrace: [], 19 | expectedType: 'object', 20 | received: typedValue, 21 | }); 22 | } else { 23 | if (typeof typedValue['t1'] !== 'number') { 24 | error.push({ 25 | propertyName: 't1', 26 | propertyChainTrace: [], 27 | expectedType: 'number', 28 | received: typedValue['t1'], 29 | }); 30 | } 31 | } 32 | return prevErrorLen !== error.length; 33 | })()) { 34 | errorCnt++; 35 | }; 36 | if ((() => { 37 | const prevErrorLen = error.length; 38 | if (typedValue == null || 39 | (typeof typedValue !== "object" && 40 | typeof typedValue !== "function")) { 41 | error.push({ 42 | propertyName: 'unionElement', 43 | propertyChainTrace: [], 44 | expectedType: 'object', 45 | received: typedValue, 46 | }); 47 | } else { 48 | if (typeof typedValue['t2'] !== 'string') { 49 | error.push({ 50 | propertyName: 't2', 51 | propertyChainTrace: [], 52 | expectedType: 'string', 53 | received: typedValue['t2'], 54 | }); 55 | } 56 | } 57 | return prevErrorLen !== error.length; 58 | })()) { 59 | errorCnt++; 60 | }; 61 | if ((() => { 62 | const prevErrorLen = error.length; 63 | if (typedValue == null || 64 | (typeof typedValue !== "object" && 65 | typeof typedValue !== "function")) { 66 | error.push({ 67 | propertyName: 'unionElement', 68 | propertyChainTrace: [], 69 | expectedType: 'object', 70 | received: typedValue, 71 | }); 72 | } else { 73 | if (typeof typedValue['t2'] !== 'string') { 74 | error.push({ 75 | propertyName: 't2', 76 | propertyChainTrace: [], 77 | expectedType: 'string', 78 | received: typedValue['t2'], 79 | }); 80 | } 81 | if (typeof typedValue['t3'] !== 'number') { 82 | error.push({ 83 | propertyName: 't3', 84 | propertyChainTrace: [], 85 | expectedType: 'number', 86 | received: typedValue['t3'], 87 | }); 88 | } 89 | if (typedValue['t4'] !== any) { 90 | error.push({ 91 | propertyName: 't4', 92 | propertyChainTrace: [], 93 | expectedType: 'any', 94 | received: typedValue['t4'], 95 | }); 96 | } 97 | } 98 | return prevErrorLen !== error.length; 99 | })()) { 100 | errorCnt++; 101 | } 102 | return errorCnt === 3; 103 | })()) { 104 | error.push({ 105 | propertyName: 'PrimitiveUnionWithObject', 106 | propertyChainTrace: [], 107 | expectedType: 'object | object | object', 108 | received: typedValue, 109 | }); 110 | } 111 | return error.length === 0 ? undefined : error; 112 | } 113 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/miniTestResults/Tuple.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { Tuple } from "../../../../tests/cases/mini/miniTest"; 3 | import { GeneratedWrongTypeErrorReport } from "../../../wrongTypeReportGenerator"; 4 | 5 | export const validateTuple = (value: unknown): GeneratedWrongTypeErrorReport | undefined => { 6 | const typedValue = value as Tuple; 7 | const error: GeneratedWrongTypeErrorReport = []; 8 | if (typedValue == null || 9 | (typeof typedValue !== "object" && 10 | typeof typedValue !== "function")) { 11 | error.push({ 12 | propertyName: 'Tuple', 13 | propertyChainTrace: [], 14 | expectedType: 'object', 15 | received: typedValue, 16 | }); 17 | } else { 18 | if (!Array.isArray(typedValue['test1'])) { 19 | error.push({ 20 | propertyName: 'test1', 21 | propertyChainTrace: [], 22 | expectedType: 'tuple', 23 | received: typedValue, 24 | }); 25 | } else { 26 | if (typeof typedValue['test1']['0'] !== 'number') { 27 | error.push({ 28 | propertyName: 'test1[0]', 29 | propertyChainTrace: [], 30 | expectedType: 'number', 31 | received: typedValue['test1'], 32 | }); 33 | } 34 | } 35 | if (!Array.isArray(typedValue['test2'])) { 36 | error.push({ 37 | propertyName: 'test2', 38 | propertyChainTrace: [], 39 | expectedType: 'tuple', 40 | received: typedValue, 41 | }); 42 | } else { 43 | if (typeof typedValue['test2']['0'] !== 'number') { 44 | error.push({ 45 | propertyName: 'test2[0]', 46 | propertyChainTrace: [], 47 | expectedType: 'number', 48 | received: typedValue['test2'], 49 | }); 50 | } 51 | if (typeof typedValue['test2']['1'] !== 'string') { 52 | error.push({ 53 | propertyName: 'test2[1]', 54 | propertyChainTrace: [], 55 | expectedType: 'string', 56 | received: typedValue['test2'], 57 | }); 58 | } 59 | } 60 | if (!Array.isArray(typedValue['test3'])) { 61 | error.push({ 62 | propertyName: 'test3', 63 | propertyChainTrace: [], 64 | expectedType: 'tuple', 65 | received: typedValue, 66 | }); 67 | } else { 68 | if ((() => { 69 | const prevErrorLen = error.length; 70 | if (typedValue['test3']['0'] == null || 71 | (typeof typedValue['test3']['0'] !== "object" && 72 | typeof typedValue['test3']['0'] !== "function")) { 73 | error.push({ 74 | propertyName: 'tupleElement', 75 | propertyChainTrace: ['test3[0]'], 76 | expectedType: 'object', 77 | received: typedValue['test3']['0'], 78 | }); 79 | } else { 80 | if (typeof typedValue['test3']['0']['t1'] !== 'number') { 81 | error.push({ 82 | propertyName: 't1', 83 | propertyChainTrace: ['test3[0]'], 84 | expectedType: 'number', 85 | received: typedValue['test3']['0']['t1'], 86 | }); 87 | } 88 | } 89 | return prevErrorLen !== error.length; 90 | })()) { 91 | error.push({ 92 | propertyName: 'test3[0]', 93 | propertyChainTrace: [], 94 | expectedType: 'object', 95 | received: typedValue['test3'], 96 | }); 97 | } 98 | } 99 | } 100 | return error.length === 0 ? undefined : error; 101 | } 102 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/miniTestResults/Union.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { Union } from "../../../../tests/cases/mini/miniTest"; 3 | import { GeneratedWrongTypeErrorReport } from "../../../wrongTypeReportGenerator"; 4 | 5 | export const validateUnion = (value: unknown): GeneratedWrongTypeErrorReport | undefined => { 6 | const typedValue = value as Union; 7 | const error: GeneratedWrongTypeErrorReport = []; 8 | if (typedValue == null || 9 | (typeof typedValue !== "object" && 10 | typeof typedValue !== "function")) { 11 | error.push({ 12 | propertyName: 'Union', 13 | propertyChainTrace: [], 14 | expectedType: 'object', 15 | received: typedValue, 16 | }); 17 | } else { 18 | if (typedValue['t1'] !== "asdf" && 19 | typedValue['t1'] !== "qwer") { 20 | error.push({ 21 | propertyName: 't1', 22 | propertyChainTrace: [], 23 | expectedType: '"asdf" | "qwer"', 24 | received: typedValue['t1'], 25 | }); 26 | } 27 | if (typeof typedValue['t2'] !== 'string' && 28 | typeof typedValue['t2'] !== 'number') { 29 | error.push({ 30 | propertyName: 't2', 31 | propertyChainTrace: [], 32 | expectedType: 'string | number', 33 | received: typedValue['t2'], 34 | }); 35 | } 36 | if (typeof typedValue['t3'] !== 'number' && 37 | typedValue['t3'] !== "sadf") { 38 | error.push({ 39 | propertyName: 't3', 40 | propertyChainTrace: [], 41 | expectedType: 'number | "sadf"', 42 | received: typedValue['t3'], 43 | }); 44 | } 45 | if (typedValue['t4'] !== "asdf" && 46 | (() => { 47 | const prevErrorLen = error.length; 48 | if (!Array.isArray(typedValue['t4'])) { 49 | error.push({ 50 | propertyName: 'unionElement', 51 | propertyChainTrace: ['t4'], 52 | expectedType: 'array', 53 | received: typedValue['t4'], 54 | }); 55 | } else { 56 | typedValue['t4'].find((elem) => { 57 | const prevErrorLen = error.length; 58 | if (typeof elem !== 'number') { 59 | error.push({ 60 | propertyName: 'arrayElement', 61 | propertyChainTrace: ['t4'], 62 | expectedType: 'number', 63 | received: elem, 64 | }); 65 | } 66 | return prevErrorLen !== error.length; 67 | }); 68 | } 69 | return prevErrorLen !== error.length; 70 | })()) { 71 | error.push({ 72 | propertyName: 't4', 73 | propertyChainTrace: [], 74 | expectedType: '"asdf" | array', 75 | received: typedValue['t4'], 76 | }); 77 | } 78 | } 79 | return error.length === 0 ? undefined : error; 80 | } 81 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/miniTestResults/UnionWithObject.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { UnionWithObject } from "../../../../tests/cases/mini/miniTest"; 3 | import { GeneratedWrongTypeErrorReport } from "../../../wrongTypeReportGenerator"; 4 | 5 | export const validateUnionWithObject = (value: unknown): GeneratedWrongTypeErrorReport | undefined => { 6 | const typedValue = value as UnionWithObject; 7 | const error: GeneratedWrongTypeErrorReport = []; 8 | if (typedValue == null || 9 | (typeof typedValue !== "object" && 10 | typeof typedValue !== "function")) { 11 | error.push({ 12 | propertyName: 'UnionWithObject', 13 | propertyChainTrace: [], 14 | expectedType: 'object', 15 | received: typedValue, 16 | }); 17 | } else { 18 | if ((() => { 19 | const error: GeneratedWrongTypeErrorReport = []; 20 | let errorCnt = 0; 21 | if ((() => { 22 | const prevErrorLen = error.length; 23 | if (typedValue['t1'] == null || 24 | (typeof typedValue['t1'] !== "object" && 25 | typeof typedValue['t1'] !== "function")) { 26 | error.push({ 27 | propertyName: 'unionElement', 28 | propertyChainTrace: ['t1'], 29 | expectedType: 'object', 30 | received: typedValue['t1'], 31 | }); 32 | } else { 33 | if (typeof typedValue['t1']['t2'] !== 'number') { 34 | error.push({ 35 | propertyName: 't2', 36 | propertyChainTrace: ['t1'], 37 | expectedType: 'number', 38 | received: typedValue['t1']['t2'], 39 | }); 40 | } 41 | } 42 | return prevErrorLen !== error.length; 43 | })()) { 44 | errorCnt++; 45 | }; 46 | if ((() => { 47 | const prevErrorLen = error.length; 48 | if (typedValue['t1'] == null || 49 | (typeof typedValue['t1'] !== "object" && 50 | typeof typedValue['t1'] !== "function")) { 51 | error.push({ 52 | propertyName: 'unionElement', 53 | propertyChainTrace: ['t1'], 54 | expectedType: 'object', 55 | received: typedValue['t1'], 56 | }); 57 | } else { 58 | if (typeof typedValue['t1']['t3'] !== 'string') { 59 | error.push({ 60 | propertyName: 't3', 61 | propertyChainTrace: ['t1'], 62 | expectedType: 'string', 63 | received: typedValue['t1']['t3'], 64 | }); 65 | } 66 | } 67 | return prevErrorLen !== error.length; 68 | })()) { 69 | errorCnt++; 70 | } 71 | return errorCnt === 2; 72 | })()) { 73 | error.push({ 74 | propertyName: 't1', 75 | propertyChainTrace: [], 76 | expectedType: 'object | object', 77 | received: typedValue['t1'], 78 | }); 79 | } 80 | } 81 | return error.length === 0 ? undefined : error; 82 | } 83 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/miniTestResults/WithGenerics.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { WithGenerics } from "../../../../tests/cases/mini/miniTest"; 3 | import { GeneratedWrongTypeErrorReport } from "../../../wrongTypeReportGenerator"; 4 | 5 | export const validateWithGenerics = (value: unknown): GeneratedWrongTypeErrorReport | undefined => { 6 | const typedValue = value as WithGenerics; 7 | const error: GeneratedWrongTypeErrorReport = []; 8 | if (typedValue == null || 9 | (typeof typedValue !== "object" && 10 | typeof typedValue !== "function")) { 11 | error.push({ 12 | propertyName: 'WithGenerics', 13 | propertyChainTrace: [], 14 | expectedType: 'object', 15 | received: typedValue, 16 | }); 17 | } else { 18 | if ((() => { 19 | const error: GeneratedWrongTypeErrorReport = []; 20 | let errorCnt = 0; 21 | if ((() => { 22 | const prevErrorLen = error.length; 23 | if (typedValue['t1'] == null || 24 | (typeof typedValue['t1'] !== "object" && 25 | typeof typedValue['t1'] !== "function")) { 26 | error.push({ 27 | propertyName: 'unionElement', 28 | propertyChainTrace: ['t1'], 29 | expectedType: 'object', 30 | received: typedValue['t1'], 31 | }); 32 | } else { 33 | if (typeof typedValue['t1']['t2'] !== 'string') { 34 | error.push({ 35 | propertyName: 't2', 36 | propertyChainTrace: ['t1'], 37 | expectedType: 'string', 38 | received: typedValue['t1']['t2'], 39 | }); 40 | } 41 | } 42 | return prevErrorLen !== error.length; 43 | })()) { 44 | errorCnt++; 45 | } 46 | return errorCnt === 1; 47 | })() && 48 | typedValue['t1'] !== null) { 49 | error.push({ 50 | propertyName: 't1', 51 | propertyChainTrace: [], 52 | expectedType: 'null | object', 53 | received: typedValue['t1'], 54 | }); 55 | } 56 | } 57 | return error.length === 0 ? undefined : error; 58 | } 59 | -------------------------------------------------------------------------------- /src/codeGenerator/tests/temporalTest.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getSourceFile, 3 | getTestCase, 4 | getTypeDeclaration, 5 | } from '../../../tests/utils'; 6 | import { AstRootNode } from '../../reporterAst'; 7 | import { getNewRootAst, getNewAstNode } from '../../reporterAst/astUtils'; 8 | import { transformer } from '../../transformer/transformer'; 9 | import { codeGenerator } from '../codeGenerator'; 10 | 11 | describe('transformer - imports', () => { 12 | const { typeDeclarations, project } = getTestCase( 13 | 'MinimumRequiredTestCase', 14 | ); 15 | 16 | test('', async () => { 17 | // Given 18 | const typeDeclaration = getTypeDeclaration( 19 | 'TEST1_COLUMN_WITH_GENERICS', 20 | typeDeclarations, 21 | ); 22 | const newAst = transformer(typeDeclaration); 23 | 24 | // When 25 | const newCode = await codeGenerator({ 26 | astRootNode: newAst, 27 | project, 28 | outFilePath: 'src/codeGenerator/tests/asdf.ts', 29 | inputSourceFile: getSourceFile('import/toBeImported', project), 30 | }); 31 | 32 | // Then 33 | }); 34 | 35 | test('TEST2_ENUMS', async () => { 36 | // Given 37 | const typeDeclaration = getTypeDeclaration( 38 | 'TEST2_ENUMS', 39 | typeDeclarations, 40 | ); 41 | const newAst = transformer(typeDeclaration); 42 | 43 | // When 44 | const newCode = await codeGenerator({ 45 | astRootNode: newAst, 46 | project, 47 | outFilePath: 'src/codeGenerator/tests/asdf2.ts', 48 | inputSourceFile: getSourceFile('import/toBeImported', project), 49 | }); 50 | 51 | // Then 52 | }); 53 | test('TEST4_ARRAY', async () => { 54 | // Given 55 | const typeDeclaration = getTypeDeclaration( 56 | 'TEST4_ARRAY', 57 | typeDeclarations, 58 | ); 59 | const newAst = transformer(typeDeclaration); 60 | 61 | // When 62 | const newCode = await codeGenerator({ 63 | astRootNode: newAst, 64 | project, 65 | outFilePath: 'src/codeGenerator/tests/asdf4.ts', 66 | inputSourceFile: getSourceFile('import/toBeImported', project), 67 | }); 68 | 69 | // Then 70 | }); 71 | test('TEST6_APPLIED_GENERIC_OBJECTS', async () => { 72 | // Given 73 | const typeDeclaration = getTypeDeclaration( 74 | 'TEST6_APPLIED_GENERIC_OBJECTS', 75 | typeDeclarations, 76 | ); 77 | const newAst = transformer(typeDeclaration); 78 | 79 | // When 80 | const newCode = await codeGenerator({ 81 | astRootNode: newAst, 82 | project, 83 | outFilePath: 'src/codeGenerator/tests/asdf6.ts', 84 | inputSourceFile: getSourceFile('import/toBeImported', project), 85 | }); 86 | 87 | // Then 88 | }); 89 | test('TEST7_APPLIED_NAMESPACE', async () => { 90 | // Given 91 | const typeDeclaration = getTypeDeclaration( 92 | 'TEST7_APPLIED_NAMESPACE', 93 | typeDeclarations, 94 | ); 95 | const newAst = transformer(typeDeclaration); 96 | 97 | // When 98 | const newCode = await codeGenerator({ 99 | astRootNode: newAst, 100 | project, 101 | outFilePath: 'src/codeGenerator/tests/asdf7.ts', 102 | inputSourceFile: getSourceFile('import/toBeImported', project), 103 | }); 104 | 105 | // Then 106 | }); 107 | test('TEST8_INTERSECTION', async () => { 108 | // Given 109 | const typeDeclaration = getTypeDeclaration( 110 | 'TEST8_INTERSECTION', 111 | typeDeclarations, 112 | ); 113 | const newAst = transformer(typeDeclaration); 114 | 115 | // When 116 | const newCode = await codeGenerator({ 117 | astRootNode: newAst, 118 | project, 119 | outFilePath: 'src/codeGenerator/tests/asdf8.ts', 120 | inputSourceFile: getSourceFile('import/toBeImported', project), 121 | }); 122 | 123 | // Then 124 | }); 125 | test('TEST9_TUPLE', async () => { 126 | // Given 127 | const typeDeclaration = getTypeDeclaration( 128 | 'TEST9_TUPLE', 129 | typeDeclarations, 130 | ); 131 | const newAst = transformer(typeDeclaration); 132 | 133 | // When 134 | const newCode = await codeGenerator({ 135 | astRootNode: newAst, 136 | project, 137 | outFilePath: 'src/codeGenerator/tests/asdf9.ts', 138 | inputSourceFile: getSourceFile('import/toBeImported', project), 139 | }); 140 | 141 | // Then 142 | }); 143 | test('TEST10_READONLY_ARRAY', async () => { 144 | // Given 145 | const typeDeclaration = getTypeDeclaration( 146 | 'TEST10_READONLY_ARRAY', 147 | typeDeclarations, 148 | ); 149 | const newAst = transformer(typeDeclaration); 150 | 151 | // When 152 | const newCode = await codeGenerator({ 153 | astRootNode: newAst, 154 | project, 155 | outFilePath: 'src/codeGenerator/tests/asdf10.ts', 156 | inputSourceFile: getSourceFile('import/toBeImported', project), 157 | }); 158 | 159 | // Then 160 | }); 161 | test('TEST11_CLASS', async () => { 162 | // Given 163 | const typeDeclaration = getTypeDeclaration( 164 | 'TEST11_CLASS', 165 | typeDeclarations, 166 | ); 167 | const newAst = transformer(typeDeclaration); 168 | 169 | // When 170 | const newCode = await codeGenerator({ 171 | astRootNode: newAst, 172 | project, 173 | outFilePath: 'src/codeGenerator/tests/asdf11.ts', 174 | inputSourceFile: getSourceFile('import/toBeImported', project), 175 | }); 176 | 177 | // Then 178 | }); 179 | }); 180 | -------------------------------------------------------------------------------- /src/codeGenerator/types/index.ts: -------------------------------------------------------------------------------- 1 | export interface EdgeWhere { 2 | columnName: string; 3 | 4 | expectedType: string; 5 | 6 | received: string; 7 | } 8 | 9 | export interface InternalWhere { 10 | columnName: string; 11 | 12 | where: Where; 13 | } 14 | 15 | export type Where = EdgeWhere | InternalWhere; 16 | 17 | export interface Report { 18 | isWrong: boolean; 19 | 20 | where: Where; 21 | } 22 | -------------------------------------------------------------------------------- /src/codeGenerator/utils.ts: -------------------------------------------------------------------------------- 1 | export const getNewStack = (stack: string[], name: string, root?: boolean) => { 2 | return root ? [] : name.includes('Element') ? [...stack] : [...stack, name]; 3 | }; 4 | 5 | export const wrapQuoteSymbol = (nameStack: string | string[], between?: string) => { 6 | if (typeof nameStack === 'string') { 7 | return `'${nameStack}'`; 8 | } 9 | return nameStack.map((str) => `'${str}'`).join(between ?? ', ') ?? ''; 10 | }; 11 | 12 | export const getName = (nameStack: string[], namePrefix?: string) => { 13 | return namePrefix ? `${namePrefix}${propertyChain(nameStack)}` : `typedValue${propertyChain(nameStack)}`; 14 | }; 15 | 16 | const propertyChain = (nameStack: string[]) => { 17 | return nameStack.reduce((acc, curr) => { 18 | return `${acc}['${curr}']`; 19 | }, ''); 20 | }; 21 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './wrongTypeReportGenerator'; 2 | -------------------------------------------------------------------------------- /src/reporterAst/astUtils.ts: -------------------------------------------------------------------------------- 1 | import { Type } from 'ts-morph'; 2 | import { AstNode, AstRootNode, Dependencies } from '.'; 3 | 4 | export const getNewAstNode = ({ 5 | name, 6 | type, 7 | argument, 8 | }: { 9 | name: string; 10 | type: string; 11 | argument?: AstNode[]; 12 | }): AstNode => { 13 | const ast: AstNode = { 14 | name, 15 | type, 16 | }; 17 | if (argument) ast.arguments = argument; 18 | return ast; 19 | }; 20 | 21 | export const getNewRootAst = ({ 22 | astNode, 23 | dependencies, 24 | }: { 25 | astNode: AstNode; 26 | dependencies: Dependencies; 27 | }): AstRootNode => { 28 | return { 29 | ast: { ...astNode }, 30 | dependencies: new Map(dependencies), 31 | }; 32 | }; 33 | 34 | export const pushNewArgument = (ast: AstNode, argument: AstNode) => { 35 | const newAst = { ...ast }; 36 | if (newAst.arguments) { 37 | const newArguments = [...newAst.arguments]; 38 | newArguments.push(argument); 39 | newAst.arguments = newArguments; 40 | return newAst; 41 | } 42 | 43 | newAst.arguments = [argument]; 44 | return newAst; 45 | }; 46 | -------------------------------------------------------------------------------- /src/reporterAst/index.ts: -------------------------------------------------------------------------------- 1 | import { SourceFile } from 'ts-morph'; 2 | 3 | type Primitives = 4 | | 'number' 5 | | 'string' 6 | | 'bigint' 7 | | 'boolean' 8 | | 'undefined' 9 | | 'symbol' 10 | | 'null'; 11 | 12 | type Object = 'object'; 13 | 14 | type Union = 'union'; 15 | 16 | type Array = 'array'; 17 | 18 | type Intersection = 'intersection'; 19 | 20 | type Tuple = 'tuple'; 21 | 22 | type Enum = 'enum'; 23 | 24 | export type NodeType = 25 | | 'unionElement' 26 | | 'arrayElement' 27 | | 'intersectionElement' 28 | | 'tupleElement' 29 | | 'objectElement' 30 | | 'enumElement'; 31 | 32 | export type ParentNode = Object | Union | Array | Intersection | Tuple | Enum; 33 | 34 | type ToImport = { 35 | [exportName: string]: string; 36 | }; 37 | 38 | /** 39 | * Which to import from which sourcefile 40 | */ 41 | export type Dependencies = Map; 42 | 43 | export interface AstRootNode { 44 | ast: AstNode; 45 | 46 | dependencies: Dependencies; 47 | } 48 | 49 | export interface AstNode { 50 | /** 51 | * Property name or name of specific type's part (union, object, etc.) 52 | */ 53 | name: NodeType | string; 54 | 55 | /** 56 | * Type of the property 57 | */ 58 | type: Primitives | Object | Union | string; 59 | 60 | /** 61 | * Children nodes 62 | */ 63 | arguments?: AstNode[]; 64 | } 65 | -------------------------------------------------------------------------------- /src/transformer/nodeParser/index.ts: -------------------------------------------------------------------------------- 1 | export * from './parseNode'; 2 | -------------------------------------------------------------------------------- /src/transformer/nodeParser/parseArray.ts: -------------------------------------------------------------------------------- 1 | import { getNewAstNode } from '../../reporterAst/astUtils'; 2 | import { ParseNode, parseNode } from './parseNode'; 3 | 4 | export const parseArray = ({ name, type, addToDependencyMap }: ParseNode) => { 5 | return getNewAstNode({ 6 | name, 7 | type: 'array', 8 | argument: [ 9 | parseNode({ 10 | name: 'arrayElement', 11 | type: type, 12 | addToDependencyMap, 13 | }), 14 | ], 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /src/transformer/nodeParser/parseClass.ts: -------------------------------------------------------------------------------- 1 | import { getNewAstNode } from '../../reporterAst/astUtils'; 2 | import { addTypeToDependencyMap } from '../../utils'; 3 | import { ParseNode, parseNode } from './parseNode'; 4 | import { Node } from 'ts-morph'; 5 | 6 | export const parseClass = ({ name, type, addToDependencyMap }: ParseNode) => { 7 | // add to dependency then use "instanceof" operator when generating code 8 | addTypeToDependencyMap(type, addToDependencyMap); 9 | 10 | const astNode = getNewAstNode({ 11 | name, 12 | type: 'class', 13 | argument: [ 14 | getNewAstNode({ 15 | name: 'classElement', 16 | type: type.getSymbol()!.getName(), 17 | }), 18 | ], 19 | }); 20 | 21 | // TODO: 22 | // But when using "instanceof" operator 23 | // it may not be possible to check the type from axios response 24 | // So, parse as a interface 25 | 26 | // const symbol = type.getSymbol(); 27 | // if (!symbol) { 28 | // throw new Error('No class Symbols'); 29 | // } 30 | 31 | // // class declaration is only one 32 | // const declaration = symbol.getDeclarations()[0]; 33 | 34 | // if (!Node.isClassDeclaration(declaration)) { 35 | // console.log(name, type, declaration); 36 | // throw new TypeError('Expected declaration to be an class declaration'); 37 | // } 38 | 39 | // // parse base 40 | // const parsedBases = declaration.getBaseTypes().map((baseType) => { 41 | // return parseNode({ 42 | // name: type.getSymbol()?.getName() as string, 43 | // type: baseType, 44 | // addToDependencyMap, 45 | // }); 46 | // }); 47 | // parsedBases.forEach((node) => { 48 | // astNode.arguments?.push(node); 49 | // }); 50 | 51 | // // parse properties 52 | // const properties = [ 53 | // ...declaration.getProperties(), 54 | // ...declaration.getMethods(), 55 | // ].map((p) => ({ 56 | // name: p.getSymbol()?.getEscapedName() ?? p.getName(), 57 | // type: p.getType(), 58 | // })); 59 | 60 | // const parsedProperties = properties.map((prop) => { 61 | // return parseNode({ 62 | // name: prop.name, 63 | // type: prop.type, 64 | // addToDependencyMap, 65 | // }); 66 | // }); 67 | // parsedProperties.forEach((node) => { 68 | // astNode.arguments?.push(node); 69 | // }); 70 | 71 | return astNode; 72 | }; 73 | -------------------------------------------------------------------------------- /src/transformer/nodeParser/parseIntersection.ts: -------------------------------------------------------------------------------- 1 | import { getNewAstNode } from '../../reporterAst/astUtils'; 2 | import { ParseNode, parseNode } from './parseNode'; 3 | 4 | export const parseIntersection = ({ 5 | name, 6 | type, 7 | addToDependencyMap, 8 | }: ParseNode) => { 9 | const types = type.getIntersectionTypes(); 10 | return getNewAstNode({ 11 | name: name, 12 | type: 'intersection', 13 | argument: types.map((innerType) => 14 | parseNode({ 15 | name: 'intersectionElement', 16 | type: innerType, 17 | addToDependencyMap, 18 | }), 19 | ), 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /src/transformer/nodeParser/parseLiteral.ts: -------------------------------------------------------------------------------- 1 | import { getNewAstNode } from '../../reporterAst/astUtils'; 2 | import { addTypeToDependencyMap } from '../../utils'; 3 | import { ParseNode } from './parseNode'; 4 | import { Node } from 'ts-morph'; 5 | 6 | export const parseLiteral = ({ name, type, addToDependencyMap }: ParseNode) => { 7 | if (type.isEnumLiteral()) { 8 | const node = type 9 | .getSymbol()! 10 | .getDeclarations() 11 | .find(Node.isEnumMember)! 12 | .getParent(); 13 | 14 | if (node === undefined || !Node.isEnumDeclaration(node)) { 15 | throw new Error('Error when parsing Enums'); 16 | } 17 | 18 | // add dependency 19 | addTypeToDependencyMap(type, addToDependencyMap); 20 | 21 | const enumName = node.getSymbol()?.getName(); 22 | const enumChild = type.getSymbol()?.getName(); 23 | 24 | return getNewAstNode({ 25 | name: 'enumElement', 26 | type: `${enumName}.${enumChild}`, 27 | }); 28 | } 29 | return getNewAstNode({ 30 | name, 31 | type: type.getText(), 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /src/transformer/nodeParser/parseNode.ts: -------------------------------------------------------------------------------- 1 | import { Type, Node } from 'ts-morph'; 2 | import { AstNode } from '../../reporterAst'; 3 | import { getNewAstNode } from '../../reporterAst/astUtils'; 4 | import { parseArray } from './parseArray'; 5 | import { parseClass } from './parseClass'; 6 | import { parseIntersection } from './parseIntersection'; 7 | import { parseLiteral } from './parseLiteral'; 8 | import { parseObject } from './parseObject'; 9 | import { parseTuple } from './parseTuple'; 10 | import { parseUnion } from './parseUnion'; 11 | import { AddToDependencyMap } from '../../utils'; 12 | 13 | export type ParseNode = { 14 | name: string; 15 | type: Type; 16 | addToDependencyMap: AddToDependencyMap; 17 | parentNode?: ParentNode; 18 | isEnum?: boolean; 19 | }; 20 | 21 | /** 22 | * input: name과 해당 컬럼의 ts-morph Type을 받아서 AST Node를 만들어주는 함수 23 | * @param name 24 | * @param type 25 | * @returns `AstNode` 26 | */ 27 | export const parseNode = ({ 28 | name, 29 | type, 30 | parentNode, 31 | addToDependencyMap, 32 | }: ParseNode): AstNode => { 33 | if (type.getText() === 'any' || type.getText() === 'unknown') { 34 | return getNewAstNode({ name, type: 'any' }); 35 | } 36 | 37 | if (type.getText() === 'never') { 38 | return getNewAstNode({ name, type: 'never' }); 39 | } 40 | 41 | if (type.isBoolean()) { 42 | return getNewAstNode({ name, type: 'boolean' }); 43 | } 44 | 45 | if (type.isUnion()) { 46 | return parseUnion({ 47 | name, 48 | type, 49 | isEnum: type.isEnum(), // Enum is considered as a union 50 | addToDependencyMap, 51 | parentNode, 52 | }); 53 | } 54 | 55 | if (type.isIntersection()) { 56 | return parseIntersection({ 57 | name, 58 | type, 59 | addToDependencyMap, 60 | }); 61 | } 62 | 63 | if (type.isArray()) { 64 | return parseArray({ 65 | name, 66 | type: type.getArrayElementType()!, 67 | addToDependencyMap, 68 | }); 69 | } 70 | 71 | if (isClassType(type)) { 72 | return parseClass({ 73 | name, 74 | type, 75 | addToDependencyMap, 76 | }); 77 | } 78 | 79 | if (type.isTuple()) { 80 | return parseTuple({ 81 | name, 82 | type, 83 | addToDependencyMap, 84 | }); 85 | } 86 | 87 | /** 88 | * interface or plain object 89 | */ 90 | if (type.isObject()) { 91 | return parseObject({ name, type, addToDependencyMap }); 92 | } 93 | 94 | if (type.isLiteral()) { 95 | return parseLiteral({ name, type, addToDependencyMap }); 96 | } 97 | 98 | return getNewAstNode({ 99 | name, 100 | type: type.getText(), 101 | }); 102 | }; 103 | 104 | const isClassType = (type: Type): boolean => { 105 | if (type.getConstructSignatures().length > 0) { 106 | return true; 107 | } 108 | 109 | const symbol = type.getSymbol(); 110 | if (symbol == null) { 111 | return false; 112 | } 113 | 114 | for (const declaration of symbol.getDeclarations()) { 115 | if (Node.isClassDeclaration(declaration)) { 116 | return true; 117 | } 118 | if ( 119 | Node.isVariableDeclaration(declaration) && 120 | declaration.getType().getConstructSignatures().length > 0 121 | ) { 122 | return true; 123 | } 124 | } 125 | 126 | return false; 127 | }; 128 | -------------------------------------------------------------------------------- /src/transformer/nodeParser/parseObject.ts: -------------------------------------------------------------------------------- 1 | import { Type, Node } from 'ts-morph'; 2 | import { ParseNode, parseNode } from './parseNode'; 3 | import { AstNode } from '../../reporterAst'; 4 | import { getNewAstNode } from '../../reporterAst/astUtils'; 5 | 6 | export const parseObject = ({ name, type, addToDependencyMap }: ParseNode) => { 7 | const astNode: AstNode = getNewAstNode({ 8 | name, 9 | type: 'object', 10 | argument: [], 11 | }); 12 | 13 | // interface, possible to extend base 14 | if (type.isInterface()) { 15 | const symbol = type.getSymbol(); 16 | if (!symbol) { 17 | throw new Error('No Interface Symbols'); 18 | } 19 | 20 | // interface merging이 가능하므로, symbol이 여러개일 수 있다. 21 | const declarations = symbol.getDeclarations(); 22 | 23 | // TODO: interface merge 지원 24 | const declaration = declarations[0]; 25 | if (!Node.isInterfaceDeclaration(declaration)) { 26 | throw new TypeError( 27 | 'Expected declaration to be an interface declaration', 28 | ); 29 | } 30 | 31 | // parse base 32 | const parsedBases = declaration.getBaseTypes().map((baseType) => { 33 | return parseNode({ 34 | name: type.getSymbol()?.getName() as string, 35 | type: baseType, 36 | addToDependencyMap, 37 | }); 38 | }); 39 | parsedBases.forEach((node) => { 40 | astNode.arguments?.push(node); 41 | }); 42 | 43 | // parse properties 44 | const properties = [ 45 | ...declaration.getProperties(), 46 | ...declaration.getMethods(), 47 | ].map((p) => ({ 48 | name: p.getSymbol()?.getEscapedName() ?? p.getName(), 49 | type: p.getType(), 50 | })); 51 | 52 | const parsedProperties = properties.map((prop) => { 53 | return parseNode({ 54 | name: prop.name, 55 | type: prop.type, 56 | addToDependencyMap, 57 | }); 58 | }); 59 | parsedProperties.forEach((node) => { 60 | astNode.arguments?.push(node); 61 | }); 62 | 63 | // parse index signatures 64 | const indexSignatures = declaration 65 | .getIndexSignatures() 66 | .map((p) => ({ keyType: p.getKeyType(), type: p.getReturnType() })); 67 | 68 | const parsedIndexSignatures = indexSignatures.map((idxSig) => { 69 | return parseNode({ 70 | name: indexKeyTypeToString(idxSig.keyType), 71 | type: idxSig.type, 72 | addToDependencyMap, 73 | }); 74 | }); 75 | parsedIndexSignatures.forEach((node) => { 76 | astNode.arguments?.push(node); 77 | }); 78 | } else { 79 | // object 80 | const properties = type.getProperties(); 81 | const typeDeclarations = type.getSymbol()?.getDeclarations(); 82 | 83 | const propertySignatures = properties.map((p) => { 84 | const propertyDeclarations = p.getDeclarations(); 85 | const typeAtLocation = 86 | propertyDeclarations.length !== 0 87 | ? p.getTypeAtLocation(propertyDeclarations[0]) 88 | : p.getTypeAtLocation((typeDeclarations || [])[0]); 89 | return { 90 | name: p.getName(), 91 | type: typeAtLocation, 92 | }; 93 | }); 94 | const parsedPropertySignatures = propertySignatures.map((propSig) => { 95 | return parseNode({ 96 | name: propSig.name, 97 | type: propSig.type, 98 | addToDependencyMap, 99 | }); 100 | }); 101 | parsedPropertySignatures.forEach((node) => { 102 | astNode.arguments?.push(node); 103 | }); 104 | 105 | const stringIndexType = type.getStringIndexType(); 106 | // TODO 107 | 108 | const numberIndexType = type.getNumberIndexType(); 109 | // TODO 110 | } 111 | 112 | return astNode; 113 | }; 114 | 115 | type IndexKeyType = 'string' | 'number' | 'any'; 116 | const indexKeyTypeToString = (type: Type): IndexKeyType => { 117 | switch (true) { 118 | case type.isString(): 119 | return 'string'; 120 | case type.isNumber(): 121 | return 'number'; 122 | case type.isAny(): 123 | return 'any'; 124 | default: 125 | throw new Error( 126 | `Invalid type for index key: ${type.getText()}. Only string or number are expected.`, 127 | ); 128 | } 129 | }; 130 | -------------------------------------------------------------------------------- /src/transformer/nodeParser/parseTuple.ts: -------------------------------------------------------------------------------- 1 | import { ParseNode, parseNode } from './parseNode'; 2 | 3 | export const parseTuple = ({ name, type, addToDependencyMap }: ParseNode) => { 4 | const types = type.getTupleElements(); 5 | return { 6 | name: name, 7 | type: 'tuple', 8 | arguments: types.map((innerType) => 9 | parseNode({ 10 | name: 'tupleElement', 11 | type: innerType, 12 | addToDependencyMap, 13 | }), 14 | ), 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /src/transformer/nodeParser/parseUnion.ts: -------------------------------------------------------------------------------- 1 | import { AstNode } from '../../reporterAst'; 2 | import { getNewAstNode } from '../../reporterAst/astUtils'; 3 | import { ParseNode, parseNode } from './parseNode'; 4 | 5 | export const parseUnion = ({ 6 | name, 7 | type, 8 | isEnum, 9 | addToDependencyMap, 10 | }: ParseNode) => { 11 | const unionElements = type.getUnionTypes(); 12 | 13 | const parsedUnionElements = unionElements.map((elem) => 14 | parseNode({ name: 'unionElement', type: elem, addToDependencyMap }), 15 | ); 16 | 17 | const unionChildren = checkBooleanTypeInLiteral( 18 | 'unionElement', 19 | parsedUnionElements, 20 | ); 21 | 22 | return getNewAstNode({ 23 | name, 24 | type: isEnum ? 'enum' : 'union', 25 | argument: unionChildren, 26 | }); 27 | }; 28 | 29 | const checkBooleanTypeInLiteral = ( 30 | name: string, 31 | unionAstNodes: AstNode[], 32 | ): AstNode[] => { 33 | // booleanLiteral => "true" | "false" 34 | // string union => "\"true\"" | "\"false\"" 35 | const copiedUnionAstNodes = [...unionAstNodes]; 36 | const withoutBooleanLiterals = copiedUnionAstNodes.filter( 37 | (node) => node.type !== 'true' && node.type !== 'false', 38 | ); 39 | 40 | // has "true" and "false" at the same time for the same column 41 | // doesn't count for "true" | "true" for now because it is hilarious 42 | const hasBooleanTypeInLiteral = 43 | copiedUnionAstNodes.length - 2 === withoutBooleanLiterals.length; 44 | 45 | if (hasBooleanTypeInLiteral) { 46 | withoutBooleanLiterals.push(getNewAstNode({ name, type: 'boolean' })); 47 | return withoutBooleanLiterals; 48 | } 49 | return copiedUnionAstNodes; 50 | }; 51 | -------------------------------------------------------------------------------- /src/transformer/tests/importTestCase.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getSourceFile, 3 | getTestCase, 4 | getTypeDeclaration, 5 | } from '../../../tests/utils'; 6 | import { AstRootNode } from '../../reporterAst'; 7 | import { getNewRootAst, getNewAstNode } from '../../reporterAst/astUtils'; 8 | import { transformer } from '../transformer'; 9 | 10 | describe('transformer - imports', () => { 11 | const { typeDeclarations, project } = getTestCase('import/importTestCase'); 12 | 13 | test('Enum should be imported properly', () => { 14 | // Given 15 | const typeDeclaration = getTypeDeclaration('Test', typeDeclarations); 16 | 17 | // When 18 | const newAst = transformer(typeDeclaration); 19 | 20 | // Then 21 | const expectedAstNode = getNewAstNode({ 22 | name: 'Test', 23 | type: 'object', 24 | argument: [ 25 | getNewAstNode({ 26 | name: 'type', 27 | type: 'enum', 28 | argument: [ 29 | getNewAstNode({ 30 | name: 'enumElement', 31 | type: 'TestType.t', 32 | }), 33 | getNewAstNode({ 34 | name: 'enumElement', 35 | type: 'TestType.a', 36 | }), 37 | getNewAstNode({ 38 | name: 'enumElement', 39 | type: 'TestType.b', 40 | }), 41 | ], 42 | }), 43 | ], 44 | }); 45 | const expectedSourceFile = getSourceFile( 46 | 'import/toBeImported', 47 | project, 48 | ); 49 | 50 | expect(newAst.ast).toEqual(expectedAstNode); 51 | expect(newAst.dependencies.has(expectedSourceFile)).toEqual(true); 52 | expect(newAst.dependencies.get(expectedSourceFile)).toEqual({ 53 | TestType: 'TestType', 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /src/transformer/tests/interfaceWithPrimitives.test.ts: -------------------------------------------------------------------------------- 1 | import { transformer } from '../transformer'; 2 | import { TypeDeclaration } from '../../types'; 3 | import { getTestCase, getTypeDeclaration } from '../../../tests/utils'; 4 | import { AstNode, AstRootNode } from '../../reporterAst'; 5 | import { getNewRootAst } from '../../reporterAst/astUtils'; 6 | 7 | describe('transformer - Interface with Primitive type tests', () => { 8 | const { typeDeclarations } = getTestCase('InterfaceWithPrimitives'); 9 | 10 | test('Interface with Primitive', () => { 11 | // Given 12 | const typeDeclaration = getTypeDeclaration( 13 | 'InterfaceWithPrimitives', 14 | typeDeclarations, 15 | ); 16 | 17 | // When 18 | const newAst = transformer(typeDeclaration); 19 | 20 | // Then 21 | const expectedResult: AstRootNode = getNewRootAst({ 22 | dependencies: new Map(), 23 | astNode: { 24 | name: 'InterfaceWithPrimitives', 25 | type: 'object', 26 | arguments: [ 27 | { name: 'number1', type: 'number' }, 28 | { name: 'string1', type: 'string' }, 29 | { name: 'bigint1', type: 'bigint' }, 30 | { name: 'boolean1', type: 'boolean' }, 31 | { name: 'undefined1', type: 'undefined' }, 32 | { name: 'symbol1', type: 'symbol' }, 33 | { name: 'null1', type: 'null' }, 34 | 35 | // with question mark 36 | { 37 | name: 'number2', 38 | type: 'union', 39 | arguments: [ 40 | { 41 | name: 'unionElement', 42 | type: 'undefined', 43 | }, 44 | { name: 'unionElement', type: 'number' }, 45 | ], 46 | }, 47 | { 48 | name: 'string2', 49 | type: 'union', 50 | 51 | arguments: [ 52 | { 53 | name: 'unionElement', 54 | type: 'undefined', 55 | }, 56 | { name: 'unionElement', type: 'string' }, 57 | ], 58 | }, 59 | { 60 | name: 'bigint2', 61 | type: 'union', 62 | 63 | arguments: [ 64 | { 65 | name: 'unionElement', 66 | type: 'undefined', 67 | }, 68 | { name: 'unionElement', type: 'bigint' }, 69 | ], 70 | }, 71 | { 72 | name: 'boolean2', 73 | type: 'union', 74 | arguments: [ 75 | { 76 | name: 'unionElement', 77 | type: 'undefined', 78 | }, 79 | { 80 | name: 'unionElement', 81 | type: 'boolean', 82 | }, 83 | ], 84 | }, 85 | { name: 'undefined2', type: 'undefined' }, 86 | { 87 | name: 'symbol2', 88 | type: 'union', 89 | arguments: [ 90 | { 91 | name: 'unionElement', 92 | type: 'undefined', 93 | }, 94 | { name: 'unionElement', type: 'symbol' }, 95 | ], 96 | }, 97 | { 98 | name: 'null2', 99 | type: 'union', 100 | 101 | arguments: [ 102 | { name: 'unionElement', type: 'undefined' }, 103 | { name: 'unionElement', type: 'null' }, 104 | ], 105 | }, 106 | ], 107 | }, 108 | }); 109 | expect(newAst).toEqual(expectedResult); 110 | }); 111 | 112 | test('Extended Interface with Primitive', () => { 113 | // Given 114 | const typeDeclaration = getTypeDeclaration( 115 | 'ExtendedInterfaceWithPrimitives', 116 | typeDeclarations, 117 | ); 118 | 119 | // When 120 | const newAst = transformer(typeDeclaration); 121 | 122 | // Then 123 | const expectedResult: AstRootNode = getNewRootAst({ 124 | dependencies: new Map(), 125 | astNode: { 126 | name: 'ExtendedInterfaceWithPrimitives', 127 | type: 'object', 128 | arguments: [ 129 | { 130 | name: 'ExtendedInterfaceWithPrimitives', 131 | type: 'object', 132 | arguments: [ 133 | { name: 'number1', type: 'number' }, 134 | { name: 'string1', type: 'string' }, 135 | { name: 'bigint1', type: 'bigint' }, 136 | { 137 | name: 'boolean1', 138 | type: 'boolean', 139 | }, 140 | { 141 | name: 'undefined1', 142 | type: 'undefined', 143 | }, 144 | { name: 'symbol1', type: 'symbol' }, 145 | { name: 'null1', type: 'null' }, 146 | 147 | // with question mark 148 | { 149 | name: 'number2', 150 | type: 'union', 151 | arguments: [ 152 | { 153 | name: 'unionElement', 154 | type: 'undefined', 155 | }, 156 | { 157 | name: 'unionElement', 158 | type: 'number', 159 | }, 160 | ], 161 | }, 162 | { 163 | name: 'string2', 164 | type: 'union', 165 | arguments: [ 166 | { 167 | name: 'unionElement', 168 | type: 'undefined', 169 | }, 170 | { 171 | name: 'unionElement', 172 | type: 'string', 173 | }, 174 | ], 175 | }, 176 | { 177 | name: 'bigint2', 178 | type: 'union', 179 | arguments: [ 180 | { 181 | name: 'unionElement', 182 | type: 'undefined', 183 | }, 184 | { 185 | name: 'unionElement', 186 | type: 'bigint', 187 | }, 188 | ], 189 | }, 190 | { 191 | name: 'boolean2', 192 | type: 'union', 193 | arguments: [ 194 | { 195 | name: 'unionElement', 196 | type: 'undefined', 197 | }, 198 | { 199 | name: 'unionElement', 200 | type: 'boolean', 201 | }, 202 | ], 203 | }, 204 | { 205 | name: 'undefined2', 206 | type: 'undefined', 207 | }, 208 | { 209 | name: 'symbol2', 210 | type: 'union', 211 | arguments: [ 212 | { 213 | name: 'unionElement', 214 | type: 'undefined', 215 | }, 216 | { 217 | name: 'unionElement', 218 | type: 'symbol', 219 | }, 220 | ], 221 | }, 222 | { 223 | name: 'null2', 224 | type: 'union', 225 | arguments: [ 226 | { 227 | name: 'unionElement', 228 | type: 'undefined', 229 | }, 230 | { 231 | name: 'unionElement', 232 | type: 'null', 233 | }, 234 | ], 235 | }, 236 | ], 237 | }, 238 | { 239 | name: 'thisisincluded', 240 | type: 'number', 241 | }, 242 | { 243 | name: 'thisisincluded2', 244 | type: 'union', 245 | arguments: [ 246 | { 247 | name: 'unionElement', 248 | type: 'undefined', 249 | }, 250 | { 251 | name: 'unionElement', 252 | type: 'number', 253 | }, 254 | ], 255 | }, 256 | ], 257 | }, 258 | }); 259 | expect(newAst).toEqual(expectedResult); 260 | }); 261 | 262 | test('Nested Interface with Primitive', () => { 263 | // Given 264 | const typeDeclaration = getTypeDeclaration( 265 | 'NestedInterfaceWithPrimitives', 266 | typeDeclarations, 267 | ); 268 | 269 | // When 270 | const newAst = transformer(typeDeclaration); 271 | 272 | // Then 273 | const expectedResult: AstRootNode = getNewRootAst({ 274 | dependencies: new Map(), 275 | astNode: { 276 | name: 'NestedInterfaceWithPrimitives', 277 | type: 'object', 278 | arguments: [ 279 | { name: 'number1', type: 'number' }, 280 | { 281 | name: 'nested', 282 | type: 'object', 283 | arguments: [ 284 | { name: 'number2', type: 'number' }, 285 | { name: 'string2', type: 'string' }, 286 | ], 287 | }, 288 | ], 289 | }, 290 | }); 291 | expect(newAst).toEqual(expectedResult); 292 | }); 293 | }); 294 | -------------------------------------------------------------------------------- /src/transformer/tests/primitiveType.test.ts: -------------------------------------------------------------------------------- 1 | import { transformer } from '../transformer'; 2 | import { getTestCase } from '../../../tests/utils/getTestCase'; 3 | import { getTypeDeclaration } from '../../../tests/utils'; 4 | import { AstRootNode } from '../../reporterAst'; 5 | import { getNewRootAst } from '../../reporterAst/astUtils'; 6 | 7 | describe('transformer - Primitive type tests', () => { 8 | const { typeDeclarations } = getTestCase('PrimitiveType'); 9 | 10 | test('Primitive type: number', () => { 11 | // Given 12 | const typeDeclaration = getTypeDeclaration( 13 | 'PrimitiveNumber', 14 | typeDeclarations, 15 | ); 16 | 17 | // When 18 | const newAst = transformer(typeDeclaration); 19 | 20 | // Then 21 | const expectedResult: AstRootNode = getNewRootAst({ 22 | dependencies: new Map(), 23 | astNode: { 24 | name: 'PrimitiveNumber', 25 | type: 'number', 26 | }, 27 | }); 28 | expect(newAst).toEqual(expectedResult); 29 | }); 30 | 31 | test('Primitive type: string', () => { 32 | // Given 33 | const typeDeclaration = getTypeDeclaration( 34 | 'PrimitiveString', 35 | typeDeclarations, 36 | ); 37 | 38 | // When 39 | const newAst = transformer(typeDeclaration); 40 | 41 | // Then 42 | const expectedResult: AstRootNode = getNewRootAst({ 43 | dependencies: new Map(), 44 | astNode: { 45 | name: 'PrimitiveString', 46 | type: 'string', 47 | }, 48 | }); 49 | expect(newAst).toEqual(expectedResult); 50 | }); 51 | 52 | test('Primitive type: bigint', () => { 53 | // Given 54 | const typeDeclaration = getTypeDeclaration( 55 | 'PrimitiveBigint', 56 | typeDeclarations, 57 | ); 58 | 59 | // When 60 | const newAst = transformer(typeDeclaration); 61 | 62 | // Then 63 | const expectedResult: AstRootNode = getNewRootAst({ 64 | dependencies: new Map(), 65 | astNode: { 66 | name: 'PrimitiveBigint', 67 | type: 'bigint', 68 | }, 69 | }); 70 | expect(newAst).toEqual(expectedResult); 71 | }); 72 | 73 | test('Primitive type: boolean', () => { 74 | // Given 75 | const typeDeclaration = getTypeDeclaration( 76 | 'PrimitiveBoolean', 77 | typeDeclarations, 78 | ); 79 | 80 | // When 81 | const newAst = transformer(typeDeclaration); 82 | 83 | // Then 84 | const expectedResult: AstRootNode = getNewRootAst({ 85 | dependencies: new Map(), 86 | astNode: { 87 | name: 'PrimitiveBoolean', 88 | type: 'boolean', 89 | }, 90 | }); 91 | expect(newAst).toEqual(expectedResult); 92 | }); 93 | 94 | test('Primitive type: undefined', () => { 95 | // Given 96 | const typeDeclaration = getTypeDeclaration( 97 | 'PrimitiveUndefined', 98 | typeDeclarations, 99 | ); 100 | 101 | // When 102 | const newAst = transformer(typeDeclaration); 103 | 104 | // Then 105 | const expectedResult: AstRootNode = getNewRootAst({ 106 | dependencies: new Map(), 107 | astNode: { 108 | name: 'PrimitiveUndefined', 109 | type: 'undefined', 110 | }, 111 | }); 112 | expect(newAst).toEqual(expectedResult); 113 | }); 114 | 115 | test('Primitive type: symbol', () => { 116 | // Given 117 | const typeDeclaration = getTypeDeclaration( 118 | 'PrimitiveSymbol', 119 | typeDeclarations, 120 | ); 121 | 122 | // When 123 | const newAst = transformer(typeDeclaration); 124 | 125 | // Then 126 | const expectedResult: AstRootNode = getNewRootAst({ 127 | dependencies: new Map(), 128 | astNode: { 129 | name: 'PrimitiveSymbol', 130 | type: 'symbol', 131 | }, 132 | }); 133 | expect(newAst).toEqual(expectedResult); 134 | }); 135 | 136 | test('Primitive type: null', () => { 137 | // Given 138 | const typeDeclaration = getTypeDeclaration( 139 | 'PrimitiveNull', 140 | typeDeclarations, 141 | ); 142 | 143 | // When 144 | const newAst = transformer(typeDeclaration); 145 | 146 | // Then 147 | const expectedResult: AstRootNode = getNewRootAst({ 148 | dependencies: new Map(), 149 | astNode: { 150 | name: 'PrimitiveNull', 151 | type: 'null', 152 | }, 153 | }); 154 | expect(newAst).toEqual(expectedResult); 155 | }); 156 | }); 157 | -------------------------------------------------------------------------------- /src/transformer/transformer.ts: -------------------------------------------------------------------------------- 1 | import { TypeDeclaration } from '../types'; 2 | import { getNewRootAst } from '../reporterAst/astUtils'; 3 | import { createAddToDependencyMap } from '../utils'; 4 | import { parseNode } from './nodeParser'; 5 | 6 | /** 7 | * input: ts-morph로 생성된 TypeDeclaration 1개 (AST) 8 | * output: Typeguard Code Generator를 위한 새로운 AST 9 | */ 10 | export const transformer = (typeDeclaration: TypeDeclaration) => { 11 | const typeName = typeDeclaration.getName()!; 12 | 13 | const { getDependencyMap, addToDependencyMap } = createAddToDependencyMap(); 14 | const astNode = parseNode({ 15 | name: typeName, 16 | type: typeDeclaration.getType(), 17 | addToDependencyMap, 18 | }); 19 | 20 | return getNewRootAst({ astNode, dependencies: getDependencyMap() }); 21 | }; 22 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | InterfaceDeclaration, 3 | TypeAliasDeclaration, 4 | EnumDeclaration, 5 | Node, 6 | ClassDeclaration, 7 | } from 'ts-morph'; 8 | 9 | export type TypeDeclaration = 10 | | InterfaceDeclaration 11 | | TypeAliasDeclaration 12 | | EnumDeclaration 13 | | ClassDeclaration; 14 | 15 | export const isTypeDeclaration = (value: Node): value is TypeDeclaration => { 16 | return ( 17 | Node.isTypeAliasDeclaration(value) || 18 | Node.isInterfaceDeclaration(value) || 19 | Node.isEnumDeclaration(value) || 20 | Node.isClassDeclaration(value) 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { ExportableNode, SourceFile, Type, Node } from 'ts-morph'; 2 | import { Dependencies } from './reporterAst'; 3 | 4 | export type AddDependency = { 5 | sourceFile: SourceFile; 6 | exportName: string; 7 | isDefault: boolean; 8 | }; 9 | 10 | export type AddToDependencyMap = (toAdd: AddDependency) => void; 11 | 12 | const findExportableNode = (type: Type): (ExportableNode & Node) | null => { 13 | const symbol = type.getSymbol(); 14 | if (symbol === undefined) { 15 | return null; 16 | } 17 | 18 | return ( 19 | symbol 20 | .getDeclarations() 21 | .reduce( 22 | (acc, node) => [...acc, node, ...node.getAncestors()], 23 | [], 24 | ) 25 | .filter(Node.isExportable) 26 | .find((n) => n.isExported()) || null 27 | ); 28 | }; 29 | 30 | export const addTypeToDependencyMap = ( 31 | type: Type, 32 | addToDependencyMap: AddToDependencyMap, 33 | ): void => { 34 | const exportable = findExportableNode(type); 35 | if (exportable === null) { 36 | return; 37 | } 38 | 39 | const sourceFile = exportable.getSourceFile(); 40 | const name = exportable.getSymbol()!.getName(); 41 | const isDefault = exportable.isDefaultExport(); 42 | 43 | if (!exportable.isExported()) { 44 | reportError(`${name} is not exported from ${sourceFile.getFilePath()}`); 45 | } 46 | 47 | addToDependencyMap({ sourceFile, exportName: name, isDefault }); 48 | }; 49 | 50 | export const createAddToDependencyMap = () => { 51 | const dependencies: Dependencies = new Map(); 52 | 53 | return { 54 | addToDependencyMap: (toAdd: AddDependency) => { 55 | const { sourceFile, exportName, isDefault } = { ...toAdd }; 56 | 57 | const alias = exportName; 58 | const name = isDefault ? 'default' : exportName; 59 | 60 | let imports = dependencies.get(sourceFile); 61 | if (imports === undefined) { 62 | imports = {}; 63 | dependencies.set(sourceFile, imports); 64 | } 65 | 66 | const previousAlias = imports[name]; 67 | if (previousAlias !== undefined && previousAlias !== alias) { 68 | reportError( 69 | `Conflicting export alias for "${sourceFile.getFilePath()}": "${alias}" vs "${previousAlias}"`, 70 | ); 71 | } 72 | 73 | imports[name] = alias; 74 | }, 75 | getDependencyMap: () => { 76 | return dependencies; 77 | }, 78 | }; 79 | }; 80 | 81 | export const makeAsync = async () => { 82 | return new Promise((resolve) => setTimeout(resolve, 0)); 83 | }; 84 | -------------------------------------------------------------------------------- /src/wrongTypeReportGenerator.ts: -------------------------------------------------------------------------------- 1 | import { Project } from 'ts-morph'; 2 | import { transformer } from './transformer/transformer'; 3 | import { isTypeDeclaration, TypeDeclaration } from './types'; 4 | import { codeGenerator } from './codeGenerator/codeGenerator'; 5 | import path from 'path'; 6 | import { makeAsync } from './utils'; 7 | 8 | export const generateOneWrongTypeReport = async ({ 9 | filePath, 10 | customProject, 11 | outDirPath, 12 | onlyExport, 13 | }: { 14 | filePath: string; 15 | customProject?: Project; 16 | outDirPath?: string; 17 | onlyExport?: boolean; 18 | }) => { 19 | await makeAsync(); 20 | if (!outDirPath) { 21 | outDirPath = path.dirname(filePath); 22 | } 23 | 24 | const project = 25 | customProject || 26 | new Project({ 27 | tsConfigFilePath: 'tsconfig.json', 28 | }); 29 | 30 | const sourceFile = project.getSourceFile(filePath); 31 | if (!sourceFile) { 32 | throw new Error(`No sourcefile at ${filePath}`); 33 | } 34 | 35 | const exports = Array.from(sourceFile.getExportedDeclarations().values()) 36 | .flat() 37 | .filter((ex) => ex.getSourceFile() === sourceFile); 38 | 39 | const exportedTypeDeclarations = exports.filter(isTypeDeclaration); 40 | 41 | // Get all type declarations 42 | const typesDeclarations: TypeDeclaration[] = onlyExport 43 | ? exportedTypeDeclarations 44 | : [...sourceFile.getInterfaces(), ...sourceFile.getTypeAliases(), ...sourceFile.getEnums()]; 45 | 46 | // Get new AST 47 | const newAsts = typesDeclarations.map((typeDeclaration) => { 48 | return transformer(typeDeclaration); 49 | }); 50 | 51 | // Generate guard then add to SourceFile 52 | await Promise.all( 53 | newAsts.map((ast) => { 54 | return codeGenerator({ 55 | astRootNode: ast, 56 | project, 57 | outFilePath: `${outDirPath}/${ast.ast.name}.guard.ts`, 58 | inputSourceFile: sourceFile, 59 | }); 60 | }), 61 | ); 62 | }; 63 | 64 | export const generateWrongTypeReport = async ({ 65 | filePaths, 66 | customProject, 67 | outDirPath, 68 | onlyExport, 69 | }: { 70 | filePaths: string[]; 71 | customProject?: Project; 72 | outDirPath?: string; 73 | onlyExport?: boolean; 74 | }) => { 75 | await makeAsync(); 76 | await Promise.all( 77 | filePaths.map((filePath) => { 78 | return generateOneWrongTypeReport({ 79 | filePath, 80 | customProject, 81 | outDirPath, 82 | onlyExport, 83 | }); 84 | }), 85 | ); 86 | }; 87 | 88 | export type GeneratedWrongTypeErrorReport = { 89 | propertyName: string; 90 | propertyChainTrace: string[]; 91 | received: any; 92 | expectedType: string; 93 | }[]; 94 | 95 | /** 96 | * generateReporter 97 | * 98 | * Generates a reporter function that takes in a reporter function generated by wrong-type-report-generator 99 | * 100 | * @param onError perform action when there is an error report 101 | * @param onNotError perform action when there is no error report 102 | * @returns Reporter function 103 | */ 104 | export const generateReporter = 105 | (onError: (errorReport: GeneratedWrongTypeErrorReport) => void, onNotError?: () => void) => 106 | /** 107 | * @param reporter function that is generated by wrong-type-report-generator 108 | * @returns function that takes in an object to validate 109 | */ 110 | (reporter: (toValidate: unknown) => GeneratedWrongTypeErrorReport | undefined) => 111 | (fromApi: T) => { 112 | try { 113 | const errorReport = reporter(fromApi); 114 | if (errorReport) { 115 | onError(errorReport); 116 | } else { 117 | onNotError?.(); 118 | } 119 | } catch {} 120 | return fromApi; 121 | }; 122 | -------------------------------------------------------------------------------- /tests/cases/InterfaceWithPrimitives.ts: -------------------------------------------------------------------------------- 1 | export interface InterfaceWithPrimitives { 2 | number1: number; 3 | string1: string; 4 | bigint1: bigint; 5 | boolean1: boolean; 6 | undefined1: undefined; 7 | symbol1: symbol; 8 | null1: null; 9 | number2?: number; 10 | string2?: string; 11 | bigint2?: bigint; 12 | boolean2?: boolean; 13 | undefined2?: undefined; 14 | symbol2?: symbol; 15 | null2?: null; 16 | } 17 | 18 | export interface ExtendedInterfaceWithPrimitives 19 | extends InterfaceWithPrimitives { 20 | thisisincluded: number; 21 | thisisincluded2?: number; 22 | } 23 | 24 | export interface NestedInterfaceWithPrimitives { 25 | number1: number; 26 | nested: { 27 | number2: number; 28 | string2: string; 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /tests/cases/MinimumRequiredTestCase.ts: -------------------------------------------------------------------------------- 1 | export type Nullable = T | null; 2 | 3 | /** 4 | * THIS IS A MINIMUM REQUIREMENT TO PASS 5 | */ 6 | export interface INTEGRATED_TEST { 7 | test1: { 8 | test11: string; 9 | test12: number; 10 | test13: 'test14' | 'test15'; 11 | test14: TEST1_COLUMN_WITH_GENERICS; 12 | test15: { 13 | test141: string; 14 | test142: number; 15 | test143: { 16 | test1431: string; 17 | }; 18 | }; 19 | }; 20 | test2: string | null; 21 | test3: Nullable; 22 | test4: Array; 23 | test5: TEST2_ENUMS; 24 | test6: Array; 25 | test7: 'test71' | 1 | TEST1_COLUMN_WITH_GENERICS | TEST2_ENUMS; 26 | test8: string[]; 27 | test9: [string]; 28 | test10: TEST1_COLUMN_WITH_GENERICS[]; 29 | test11: TEST2_ENUMS[]; 30 | test12: Array<{ 31 | test121: string; 32 | test122: number; 33 | }>; 34 | test13: { 35 | test121: string; 36 | test122: number; 37 | }[]; 38 | test14: 1; 39 | test15: '1'; 40 | test16: TEST3_NAMESPACE.TEST31; 41 | test17: TEST5_GENERICS; 42 | test18: TEST5_GENERICS; 43 | test19: TEST6_APPLIED_GENERIC_OBJECTS; 44 | test20: TEST7_APPLIED_NAMESPACE; 45 | test21: TEST8_INTERSECTION; 46 | } 47 | 48 | export interface TEST1_COLUMN_WITH_GENERICS { 49 | int1: string; 50 | int2: Nullable<{ 51 | int3: string; 52 | }>; 53 | } 54 | 55 | export enum TEST2_ENUMS { 56 | test1 = 'TEXT', 57 | test2 = 1, 58 | test3, 59 | } 60 | 61 | export namespace TEST3_NAMESPACE { 62 | export interface TEST31 { 63 | test311: string | { id: number; image: string; description: string }[]; 64 | test312: string; 65 | } 66 | } 67 | 68 | export interface TEST4_ARRAY { 69 | test41: Array; 70 | test42: (keyof TEST1_COLUMN_WITH_GENERICS)[]; 71 | } 72 | 73 | export type TEST4_1_ARRAY_WITH_OBJECT = Array; 74 | 75 | export interface TEST5_GENERICS { 76 | test1: string; 77 | test2: T; 78 | test3: Array; 79 | } 80 | 81 | export interface TEST6_APPLIED_GENERIC_OBJECTS { 82 | test1: TEST5_GENERICS; 83 | test2: TEST5_GENERICS; 84 | } 85 | 86 | export interface TEST7_APPLIED_NAMESPACE { 87 | test1: TEST3_NAMESPACE.TEST31; 88 | } 89 | 90 | export interface TEST8_INTERSECTION { 91 | test1: { 92 | a: string; 93 | b: number; 94 | c: 'asdf'; 95 | } & { 96 | b: number; 97 | c: 'asdf'; 98 | d: { 99 | e: string; 100 | }; 101 | }; 102 | } 103 | 104 | export interface TEST9_TUPLE { 105 | test1: [number]; 106 | test2: [number, TEST2_ENUMS]; 107 | } 108 | 109 | export interface TEST10_READONLY_ARRAY { 110 | test1: ReadonlyArray; 111 | readonly test2: ReadonlyArray; 112 | readonly test3: number[]; 113 | } 114 | 115 | export class TEST11_CLASS { 116 | test1: string; 117 | test2: number; 118 | test3: TEST1_COLUMN_WITH_GENERICS; 119 | constructor() { 120 | this.test1 = 'test1'; 121 | this.test2 = 1; 122 | this.test3 = { 123 | int1: 'test1', 124 | int2: null, 125 | }; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /tests/cases/PrimitiveType.ts: -------------------------------------------------------------------------------- 1 | export type PrimitiveNumber = number; 2 | 3 | export type PrimitiveString = string; 4 | 5 | export type PrimitiveBigint = bigint; 6 | 7 | export type PrimitiveBoolean = boolean; 8 | 9 | export type PrimitiveUndefined = undefined; 10 | 11 | export type PrimitiveSymbol = symbol; 12 | 13 | export type PrimitiveNull = null; 14 | -------------------------------------------------------------------------------- /tests/cases/adds_type_guard_import_to_source_file_and_also_exports.ts: -------------------------------------------------------------------------------- 1 | export interface Empty {} 2 | -------------------------------------------------------------------------------- /tests/cases/allows_the_name_of_the_guard_file_file_to_be_specified.ts: -------------------------------------------------------------------------------- 1 | export interface Foo { 2 | foo: number; 3 | bar: string; 4 | } 5 | -------------------------------------------------------------------------------- /tests/cases/any_and_unknown_work_in_interesction_types.ts: -------------------------------------------------------------------------------- 1 | type anyType = any; 2 | type unknownType = unknown; 3 | 4 | export type AnyAndString = string & anyType; 5 | export type UnknownAndString = string & unknownType; 6 | export type AnyAndUnknownAndString = string & anyType & unknownType; 7 | -------------------------------------------------------------------------------- /tests/cases/any_and_unknown_work_in_union_types.ts: -------------------------------------------------------------------------------- 1 | // any and unknown work in union types 2 | type anyType = any; 3 | type unknownType = unknown; 4 | 5 | export type AnyOrString = string | anyType; 6 | export type UnknownOrString = string | unknownType; 7 | export type AnyOrUnknownOrString = string | anyType | unknownType; 8 | -------------------------------------------------------------------------------- /tests/cases/check_if_any_callable_properties_is_a_function.ts: -------------------------------------------------------------------------------- 1 | // 'Check if any callable properties is a function', 2 | export interface TestType { 3 | test: () => void; 4 | // ts-auto-guard-suppress function-type 5 | test2(someArg: number): boolean; 6 | // some other comments 7 | test3: { 8 | (someArg: string): number; 9 | test3Arg: number; 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /tests/cases/check_if_callable_interface_is_a_function.ts: -------------------------------------------------------------------------------- 1 | // 'Check if callable interface is a function', 2 | export interface TestType { 3 | (someArg: string): number; 4 | arg: number; 5 | } 6 | -------------------------------------------------------------------------------- /tests/cases/correctly_handles_default_export.ts: -------------------------------------------------------------------------------- 1 | // 'correctly handles default export', 2 | 3 | interface Foo { 4 | foo: number; 5 | bar: string; 6 | } 7 | 8 | export default Foo; 9 | -------------------------------------------------------------------------------- /tests/cases/deals_with_unknown_type_as_it_would_any.ts: -------------------------------------------------------------------------------- 1 | // 'Deals with unknown type as it would any', 2 | export interface TestType { 3 | [index: string]: unknown; 4 | } 5 | -------------------------------------------------------------------------------- /tests/cases/does_not_generate_empty_guard_files.ts: -------------------------------------------------------------------------------- 1 | // 'Does not generate empty guard files', 2 | -------------------------------------------------------------------------------- /tests/cases/does_not_touch_guardts_files_that_are_not_autogenerated.ts: -------------------------------------------------------------------------------- 1 | // 'does not touch .guard.ts files that are not autogenerated', 2 | alert('hello'); 3 | -------------------------------------------------------------------------------- /tests/cases/duplicate_guard_re-exports.ts: -------------------------------------------------------------------------------- 1 | // 'guards should only be generated for types that are declared, exported, and annotated in the current file', 2 | // { 3 | // 'custom.ts': ` 4 | // /** @see {isColor} ts-auto-guard:type-guard */ 5 | // export type Color = 'red' | 'blue' | 'green'`, 6 | // 'index.ts': ` 7 | // export { Color } from './custom'`, 8 | // }, 9 | -------------------------------------------------------------------------------- /tests/cases/generated_type_guards_for_arrays_of_any.ts: -------------------------------------------------------------------------------- 1 | // 'generated type guards for arrays of any', 2 | export interface Foo { 3 | value: any[]; 4 | } 5 | -------------------------------------------------------------------------------- /tests/cases/generated_type_guards_for_discriminated_unions.ts: -------------------------------------------------------------------------------- 1 | // 'generated type guards for discriminated unions', 2 | export type X = { type: 'a'; value: number } | { type: 'b'; value: string }; 3 | -------------------------------------------------------------------------------- /tests/cases/generated_type_guards_for_enums.ts: -------------------------------------------------------------------------------- 1 | // 'generated type guards for enums', 2 | export enum Types { 3 | TheGood, 4 | TheBad, 5 | TheTypeSafe, 6 | } 7 | -------------------------------------------------------------------------------- /tests/cases/generated_type_guards_for_intersection_type.ts: -------------------------------------------------------------------------------- 1 | // 'generated type guards for intersection type', 2 | export type X = { foo: number } & { bar: string }; 3 | -------------------------------------------------------------------------------- /tests/cases/generated_type_guards_for_nested_arrays.ts: -------------------------------------------------------------------------------- 1 | // 'generated type guards for nested arrays', 2 | export type Foo = { 3 | value: Array<{ 4 | value: Array; 5 | }>; 6 | }; 7 | -------------------------------------------------------------------------------- /tests/cases/generated_type_guards_for_numeric_enums_in_optional_records.ts: -------------------------------------------------------------------------------- 1 | // 'generated type guards for numeric enums in optional records', 2 | export enum Types { 3 | TheGood = 1, 4 | TheBad, 5 | TheTypeSafe, 6 | } 7 | export interface TestItem { 8 | room: Partial>; 9 | } 10 | -------------------------------------------------------------------------------- /tests/cases/generated_type_guards_with_a_short_circuit_are_correctly_stripped_by_UglifyJS.ts: -------------------------------------------------------------------------------- 1 | // 'generated type guards with a short circuit are correctly stripped by UglifyJS', 2 | export type Foo = { 3 | foo: number; 4 | bar: Foo | string | (() => void); 5 | baz: 'foo' | 'bar'; 6 | }; 7 | -------------------------------------------------------------------------------- /tests/cases/generates_tuples.ts: -------------------------------------------------------------------------------- 1 | // 'generates tuples', 2 | export interface A { 3 | b: [number]; 4 | } 5 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_a_Pick_type.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for a Pick<> type', 2 | interface Bar { 3 | foo: number; 4 | bar: number; 5 | } 6 | 7 | export type Foo = Pick; 8 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_an_object_literal_type.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for an object literal type', 2 | export type Foo = { 3 | foo: number; 4 | }; 5 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_boolean.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for boolean', 2 | export type Bool = boolean; 3 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_dynamic_object_keys,_including_when_mixed_with_static_keys.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for dynamic object keys, including when mixed with static keys', 2 | export interface TestType { 3 | // someKey: 'some' | 'key'; 4 | // [index: string]: 'dynamic' | 'string'; 5 | // [index: number]: 'also-dynamic' | 'number'; 6 | } 7 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_empty_object_if_exportAll_is_true.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for empty object if exportAll is true', 2 | export interface Empty {} 3 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_interface_extending_object_type.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for interface extending object type', 2 | export type Bar = { 3 | bar: number; 4 | }; 5 | 6 | export interface Foo extends Bar { 7 | foo: number; 8 | } 9 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_interface_extending_object_type_with_type_guard.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for interface extending object type with type guard', 2 | export type Bar = { 3 | bar: number; 4 | }; 5 | 6 | export interface Foo extends Bar { 7 | foo: number; 8 | } 9 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_interface_extending_other_interface.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for interface extending other interface', 2 | interface Bar { 3 | bar: number; 4 | } 5 | 6 | export interface Foo extends Bar { 7 | foo: number; 8 | } 9 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_interface_extending_other_interface_with_type_guard.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for interface extending other interface with type guard', 2 | export interface Bar { 3 | bar: number; 4 | } 5 | 6 | export interface Foo extends Bar { 7 | foo: number; 8 | } 9 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_interface_properties_with_numerical_names.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for interface properties with numerical names', 2 | export interface Foo { 3 | '1': number; 4 | '2': string; 5 | } 6 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_interface_property_with_empty_string_as_name.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for interface property with empty string as name', 2 | export interface Foo { 3 | '': number; 4 | } 5 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_interface_property_with_quoted_strings_as_names.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for interface property with quoted strings as names', 2 | export interface Foo { 3 | 'single-quoted': number; 4 | 'double-quoted': number; 5 | } 6 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_interface_with_optional_field.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for interface with optional field', 2 | 3 | export interface Foo { 4 | foo?: number; 5 | bar: number | undefined; 6 | baz?: number | undefined; 7 | } 8 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_mapped_types.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for mapped types', 2 | 3 | export type PropertyValueType = { value: string }; 4 | 5 | export type PropertyName = 'name' | 'value'; 6 | 7 | export type Foo = { 8 | [key in PropertyName]: PropertyValueType; 9 | }; 10 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_nested_interface.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for nested interface', 2 | interface Bar { 3 | bar: number; 4 | } 5 | 6 | export interface Foo { 7 | foo: Bar; 8 | } 9 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_nested_interface_with_type_guard.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for nested interface with type guard', 2 | 3 | export interface Bar { 4 | bar: number; 5 | } 6 | 7 | export interface Foo { 8 | foo: Bar; 9 | } 10 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_property_with_non_alphanumeric_name.ts: -------------------------------------------------------------------------------- 1 | // characters that are currently not supported include double quotes, backslashes and newlines 2 | // `generates type guards for interface property with non-alphanumeric name '${propertyName}'`, 3 | export interface Foo { 4 | '\0': number; 5 | ' ': number; 6 | '-': number; 7 | '+': number; 8 | '*': number; 9 | '/': number; 10 | '.': number; 11 | 'foo bar': number; 12 | 'foo-bar': number; 13 | 'foo+bar': number; 14 | 'foo/bar': number; 15 | 'foo.bar': number; 16 | "'foobar'": number; 17 | '#hashtag': number; 18 | '1337_leadingNumbers': number; 19 | } 20 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_record_types.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for Record types', 2 | export type TestType = Record; 3 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_recursive_types.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for recursive types', 2 | 3 | export type Branch1 = Branch1[] | string; 4 | 5 | export type Branch2 = { branches: Branch2[] } | string; 6 | 7 | export type Branch3 = 8 | | { branches: Branch3[] } 9 | | { branches: Branch3 }[] 10 | | string; 11 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_simple_interface.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for simple interface', 2 | 3 | export interface Foo { 4 | foo: number; 5 | bar: string; 6 | } 7 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_type_properties_with_numerical_names.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for type properties with numerical names', 2 | 3 | export type Foo = { 4 | '1': number; 5 | '2': string; 6 | }; 7 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_for_type_property_with_empty_string_as_name.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards for type property with empty string as name', 2 | 3 | export type Foo = { 4 | '': number; 5 | }; 6 | -------------------------------------------------------------------------------- /tests/cases/generates_type_guards_with_a_short_circuit.ts: -------------------------------------------------------------------------------- 1 | // 'generates type guards with a short circuit', 2 | 3 | export type Foo = { 4 | foo: number; 5 | }; 6 | -------------------------------------------------------------------------------- /tests/cases/import/importTestCase.ts: -------------------------------------------------------------------------------- 1 | import { TEST4_ARRAY } from '../MinimumRequiredTestCase'; 2 | import { TestType } from './toBeImported'; 3 | 4 | export interface Test { 5 | type: TestType; 6 | } 7 | 8 | const a = () => { 9 | const val = '' as unknown as TEST4_ARRAY; 10 | if (!Array.isArray(val.test41)) { 11 | // ~~ 12 | } 13 | 14 | const a = val.test41.find((elem) => { 15 | // ~~~ 16 | }); 17 | 18 | if (a) { 19 | return false; 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /tests/cases/import/toBeImported.ts: -------------------------------------------------------------------------------- 1 | export enum TestType { 2 | t = 'string', 3 | a = 'asdf', 4 | b = 'ssss', 5 | } 6 | -------------------------------------------------------------------------------- /tests/cases/imports_and_uses_generated_type_guard_if_the_type_is_used_in_another_file.ts: -------------------------------------------------------------------------------- 1 | // 'imports and uses generated type guard if the type is used in another file', 2 | 3 | export interface TestType { 4 | someKey: string | number; 5 | } 6 | // 'test-list.ts': ` 7 | // import { TestType } from './test'; 8 | 9 | // export type TestTypeList = Array; 10 | -------------------------------------------------------------------------------- /tests/cases/mini/miniTest.ts: -------------------------------------------------------------------------------- 1 | import { Nullable } from '../MinimumRequiredTestCase'; 2 | 3 | export interface OneObject { 4 | t1: number; 5 | } 6 | 7 | export interface WithGenerics { 8 | t1: Nullable<{ 9 | t2: string; 10 | }>; 11 | } 12 | 13 | export enum Enums { 14 | test1 = 'test1', 15 | test2 = 1, 16 | } 17 | 18 | namespace Namespace { 19 | export interface Object { 20 | t1: number; 21 | } 22 | } 23 | 24 | export interface NameSpace { 25 | t1: Namespace.Object; 26 | } 27 | 28 | export type PrimitiveOneArray = number[]; 29 | export type PrimitiveArrays = number[][]; 30 | export type PrimitiveOneArrayWithObject = { 31 | t1: number; 32 | }[]; 33 | export type PrimitiveArraysWithObject = { 34 | t1: number; 35 | }[][]; 36 | 37 | export interface OneArray { 38 | t1: number[]; 39 | } 40 | 41 | export interface Arrays { 42 | t1: number[][]; 43 | t2: number[][][]; 44 | } 45 | 46 | export interface OneArrayWithObject { 47 | t1: { 48 | t2: number; 49 | }[]; 50 | } 51 | 52 | export interface ArraysWithObject { 53 | t1: { 54 | t2: number; 55 | }[][]; 56 | } 57 | 58 | export type PrimitiveUnion = 'asdf' | 'qwer' | number; 59 | export type PrimitiveUnionWithObject = 60 | | { 61 | t1: number; 62 | } 63 | | { 64 | t2: string; 65 | } 66 | | { 67 | t2: string; 68 | t3: number; 69 | t4: any; 70 | }; 71 | 72 | export interface Union { 73 | t1: 'asdf' | 'qwer'; 74 | t2: number | string; 75 | t3: 'sadf' | number; 76 | t4: number[] | 'asdf'; 77 | } 78 | 79 | export interface UnionWithObject { 80 | t1: 81 | | { 82 | t2: number; 83 | } 84 | | { 85 | t3: string; 86 | }; 87 | } 88 | 89 | export type PrimitiveIntersection = { 90 | t1: string; 91 | } & { 92 | t2: number; 93 | }; 94 | 95 | export interface Intersection { 96 | t1: { 97 | t2: string; 98 | } & { 99 | t3: number; 100 | }; 101 | } 102 | 103 | export interface Tuple { 104 | test1: [number]; 105 | test2: [number, string]; 106 | test3: [OneObject]; 107 | } 108 | -------------------------------------------------------------------------------- /tests/cases/works_for_any_type.ts: -------------------------------------------------------------------------------- 1 | // 'works for any type', 2 | export type A = any; 3 | -------------------------------------------------------------------------------- /tests/cases/works_for_unknown_type.ts: -------------------------------------------------------------------------------- 1 | // 'works for unknown type', 2 | export type A = unknown; 3 | -------------------------------------------------------------------------------- /tests/utils/getSourceFile.ts: -------------------------------------------------------------------------------- 1 | import { Project } from 'ts-morph'; 2 | 3 | export const getSourceFile = (caseName: string, project: Project) => { 4 | const sourceFile = project.getSourceFile(`tests/cases/${caseName}.ts`); 5 | 6 | if (!sourceFile) { 7 | throw new Error('No test sourcefile'); 8 | } 9 | 10 | return sourceFile; 11 | }; 12 | -------------------------------------------------------------------------------- /tests/utils/getTestCase.ts: -------------------------------------------------------------------------------- 1 | import { Project } from 'ts-morph'; 2 | import { getSourceFile } from '.'; 3 | import { isTypeDeclaration } from '../../src/types'; 4 | 5 | export const getTestCase = (caseName: string) => { 6 | const testProject = new Project({ 7 | tsConfigFilePath: 'tsconfig.json', 8 | }); 9 | 10 | const testSourcefile = getSourceFile(caseName, testProject); 11 | 12 | const exports = Array.from( 13 | testSourcefile.getExportedDeclarations().values(), 14 | ) 15 | .flat() 16 | .filter((ex) => ex.getSourceFile() === testSourcefile); 17 | 18 | const typeDeclarations = exports.filter(isTypeDeclaration); 19 | 20 | return { typeDeclarations, project: testProject }; 21 | }; 22 | -------------------------------------------------------------------------------- /tests/utils/getTypeDeclaration.ts: -------------------------------------------------------------------------------- 1 | import { TypeDeclaration } from '../../src/types'; 2 | 3 | export const getTypeDeclaration = ( 4 | typeName: string, 5 | typeDeclarations: TypeDeclaration[], 6 | ) => { 7 | const typeDeclaration = typeDeclarations.find( 8 | (val) => val.getName() === typeName, 9 | ); 10 | 11 | if (!typeDeclaration) { 12 | throw new Error(`There is no TypeDeclaration named ${typeName}`); 13 | } 14 | 15 | return typeDeclaration; 16 | }; 17 | -------------------------------------------------------------------------------- /tests/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getTestCase'; 2 | export * from './getTypeDeclaration'; 3 | export * from './getSourceFile'; 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs" /* Specify what module code is generated. */, 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */, 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | "outDir": "./dist" /* Specify an output folder for all emitted files. */, 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 77 | 78 | /* Type Checking */ 79 | "strict": true /* Enable all strict type-checking options. */, 80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | } 103 | // "exclude": ["tests/*", "node_modules/*", "dist/*", "src/**/tests/*"] 104 | } 105 | -------------------------------------------------------------------------------- /tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs" /* Specify what module code is generated. */, 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */, 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | "outDir": "./dist" /* Specify an output folder for all emitted files. */, 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 77 | 78 | /* Type Checking */ 79 | "strict": true /* Enable all strict type-checking options. */, 80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | }, 103 | "exclude": ["tests/*", "node_modules/*", "dist/*", "src/**/tests/*"] 104 | } 105 | --------------------------------------------------------------------------------