├── .gitignore ├── .idea ├── .gitignore ├── .name ├── ShaderPeeper.iml ├── misc.xml ├── modules.xml └── vcs.xml ├── README.md ├── babel.config.js ├── package.json ├── src ├── ShaderPeeper_Core │ ├── Controller │ │ ├── GLSLPeepAnalyzer.ts │ │ ├── GLSLPeepCodeGenerator.ts │ │ └── ICodeAnalyzer.ts │ └── Data │ │ ├── CursorPos.ts │ │ ├── DefinitionData.ts │ │ ├── PeepAnalyzeResult.ts │ │ ├── ShaderDepthData.ts │ │ ├── ShaderPeeperRegex.ts │ │ └── VariableType.ts └── ShaderPeeper_Test │ ├── analyze.test.ts │ └── sample.test.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | ShaderPeeper -------------------------------------------------------------------------------- /.idea/ShaderPeeper.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GLSLibrary 2 | 3 | 4 | 5 | ## About 6 | ShaderPeeper is the package that generate variable-rendering glsl code. 7 | It enable us debugging parameter in main function. 8 | ## Feature 9 | - enable us to renering-debug of variable in main function. 10 | - generate glsl-code which render variable with cursor position. 11 | [![Image from Gyazo](https://i.gyazo.com/45010fc431f1a978c901d727a77ae55c.gif)](https://gyazo.com/45010fc431f1a978c901d727a77ae55c) 12 | ## Usage 13 | - Install 14 | ```bash 15 | yarn add https://github.com/Hirai0827/ShaderCutter 16 | ``` 17 | when the package is installed, compile from ts to js will automatically begin. 18 | - Use 19 | ```typescript 20 | const a = GLSLPeepAnalyzer.Analyze(code,{row:2,column:6}); 21 | if(a){ 22 | const generated = GLSLPeepCodeGenerator.Generate(a); 23 | } 24 | ``` 25 | ## Contact 26 | If you have something about the project please contact us 27 | - Hirai0827([@lucknknock](https://twitter.com/lucknknock)) 28 | Image from Gyazo 29 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [['@babel/preset-env', {targets: {node: 'current'}}],'@babel/preset-typescript'], 3 | }; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ShaderPeeper", 3 | "version": "1.0.0", 4 | "main": "build/index.js", 5 | "files": [ 6 | "build" 7 | ], 8 | "scripts": { 9 | "build": "tsc", 10 | "prepare": "npm run build" 11 | }, 12 | "author": "hirai ", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "@babel/core": "^7.12.10", 16 | "@babel/preset-env": "^7.12.11", 17 | "@babel/preset-typescript": "^7.12.7", 18 | "babel-jest": "^26.6.3", 19 | "jest": "^26.6.3", 20 | "typescript": "^4.1.3" 21 | }, 22 | "dependencies": { 23 | "@types/jest": "^26.0.19" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ShaderPeeper_Core/Controller/GLSLPeepAnalyzer.ts: -------------------------------------------------------------------------------- 1 | import {PeepAnalyzeResult, SplitShader} from "../Data/PeepAnalyzeResult"; 2 | import {CursorPos} from "../Data/CursorPos"; 3 | import {ShaderDepthMap} from "../Data/ShaderDepthData"; 4 | import {DefinitionData} from "../Data/DefinitionData"; 5 | import {ShaderPeeperRegex} from "../Data/ShaderPeeperRegex"; 6 | import {VariableType} from "../Data/VariableType"; 7 | 8 | export class GLSLPeepAnalyzer{ 9 | static Analyze:(src:string,cursorPos:CursorPos) => PeepAnalyzeResult|false = (src:string, cursorPos:CursorPos) => { 10 | const splitCode = GLSLPeepAnalyzer.SplitShader(src); 11 | if(!splitCode){ 12 | return false; 13 | } 14 | const index = GLSLPeepAnalyzer.cursorPos2Index(src,cursorPos) - splitCode.prefix.length; 15 | const depthMap = GLSLPeepAnalyzer.AnalyzeDepth(splitCode.mainFunc); 16 | const depthIndex = GLSLPeepAnalyzer.GetDepthMapIndex(depthMap,index); 17 | const definitionData = GLSLPeepAnalyzer.AnalyzeLocalVariable(depthMap); 18 | 19 | return { 20 | src:src,cursorPos:cursorPos,splitShader:splitCode,index:index,depthMap:depthMap,depthMapIndex:depthIndex,definitionData:definitionData 21 | } as PeepAnalyzeResult; 22 | }; 23 | 24 | static SplitShader:(src:string) => SplitShader|false = (src:string) => { 25 | //TODO Shaderのメイン関数のみ分離 正規表現のprefixを利用する(予定) 26 | const mainPartRegRes = ShaderPeeperRegex.mainPartRegex.exec(src); 27 | if(mainPartRegRes){ 28 | const beginPoint = mainPartRegRes.index + mainPartRegRes[0].length; 29 | const prefix = src.substring(0,beginPoint); 30 | const mainAndSuff = src.substring(beginPoint,src.length); 31 | let bracketCount = 1; 32 | let endPoint = mainAndSuff.length; 33 | for(let i = 0; i < mainAndSuff.length; i++){ 34 | switch (mainAndSuff[i]) { 35 | case '{': 36 | bracketCount++; 37 | break; 38 | case '}': 39 | bracketCount--; 40 | break; 41 | } 42 | if(bracketCount == 0){ 43 | //ここまでメイン関数 44 | endPoint = i; 45 | break; 46 | } 47 | } 48 | const main = mainAndSuff.substring(0,endPoint); 49 | const suffix = mainAndSuff.substring(endPoint,mainAndSuff.length); 50 | //TODO main関数とSuffix部分の分離 51 | return {prefix:prefix,mainFunc:main,suffix:suffix}; 52 | }else{ 53 | return false; 54 | } 55 | }; 56 | 57 | static cursorPos2Index:(src:string,cursorPos:CursorPos) => number = (src:string,cursorPos:CursorPos) => { 58 | //カーソル位置からindexを生成する 59 | const splited = src.split('\n'); 60 | let count = 0; 61 | if(cursorPos.row != 0){ 62 | for(let i = 0; i < cursorPos.row; i++){ 63 | if(!splited[i]){ 64 | break; 65 | } 66 | count += splited[i].length + 1; 67 | } 68 | } 69 | count += cursorPos.column; 70 | return count; 71 | }; 72 | 73 | static AnalyzeDepth:(src:string) => ShaderDepthMap = (src:string) => { 74 | //Shaderのネスト深度を測る 75 | const analyzeDepth = [] as ShaderDepthMap; 76 | let buffer:string = ""; 77 | let currentDepth = 0; 78 | for(let i = 0; i < src.length; i++){ 79 | switch (src[i]) { 80 | case "{": 81 | analyzeDepth.push({src:buffer + "{",depth:currentDepth}); 82 | buffer = ""; 83 | currentDepth++; 84 | break; 85 | case "}": 86 | analyzeDepth.push({src:buffer + "}",depth:currentDepth}); 87 | buffer = ""; 88 | currentDepth--; 89 | break; 90 | case ";": 91 | analyzeDepth.push({src:buffer + ";",depth:currentDepth}); 92 | buffer = ""; 93 | break; 94 | default: 95 | buffer += src[i]; 96 | break; 97 | } 98 | } 99 | if(buffer != ""){ 100 | analyzeDepth.push({src:buffer,depth:currentDepth}); 101 | } 102 | return analyzeDepth; 103 | }; 104 | 105 | static GetDepthMapIndex:(depthMap:ShaderDepthMap,index:number) => number = (depthMap:ShaderDepthMap,index:number) => { 106 | let count = 0; 107 | for(let i = 0; i < depthMap.length;i++){ 108 | count += depthMap[i].src.length; 109 | if(count >= index){ 110 | return i; 111 | } 112 | } 113 | return depthMap.length - 1; 114 | }; 115 | static AnalyzeLocalVariable:(depthMap:ShaderDepthMap) => DefinitionData = (depthMap:ShaderDepthMap) => { 116 | //TODO ローカル変数の分析(対象変数の決定をするため) 117 | const definitionData:DefinitionData = {}; 118 | definitionData[0] = {}; 119 | definitionData[0]["gl_FragCoord"] = "vec4"; 120 | definitionData[0]["gl_FragColor"] = "vec4"; 121 | 122 | for(let i = 0; i < depthMap.length; i++){ 123 | const depthData = depthMap[i]; 124 | const data = ShaderPeeperRegex.defineRegex.exec(depthData.src); 125 | if(data){ 126 | if(!definitionData[depthData.depth]){ 127 | definitionData[depthData.depth] = {}; 128 | } 129 | definitionData[depthData.depth][data[2]] = data[1] as VariableType; 130 | } 131 | } 132 | return definitionData; 133 | }; 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/ShaderPeeper_Core/Controller/GLSLPeepCodeGenerator.ts: -------------------------------------------------------------------------------- 1 | import {PeepAnalyzeResult} from "../Data/PeepAnalyzeResult"; 2 | import {VariableType} from "../Data/VariableType"; 3 | import {ShaderDepthData} from "../Data/ShaderDepthData"; 4 | import {DefinitionData} from "../Data/DefinitionData"; 5 | import {ShaderPeeperRegex} from "../Data/ShaderPeeperRegex"; 6 | 7 | 8 | type VariableTypeAndName = {type:VariableType,name:string}; 9 | 10 | export class GLSLPeepCodeGenerator { 11 | static Generate:(result:PeepAnalyzeResult) => string = (result:PeepAnalyzeResult) => { 12 | let code = ""; 13 | let targetDepth = result.depthMap[result.depthMapIndex].depth; 14 | const variableInfo = GLSLPeepCodeGenerator.GetVariableTypeAndName(result.depthMap[result.depthMapIndex],result.definitionData); 15 | if(variableInfo == false){ 16 | return ""; 17 | } 18 | //コードをフラグメントから復元 19 | for(let i = 0; i <= result.depthMapIndex; i++){ 20 | code += result.depthMap[i].src; 21 | } 22 | if(result.depthMapIndex != result.depthMap.length){ 23 | if(targetDepth != 0){ 24 | for(let i = result.depthMapIndex + 1;i < result.depthMap.length; i++){ 25 | if(result.depthMap[i].depth < targetDepth){ 26 | break; 27 | }else{ 28 | code += result.depthMap[i].src; 29 | } 30 | } 31 | } 32 | } 33 | 34 | code += "\n"+GLSLPeepCodeGenerator.GenerateSuffixCode(variableInfo.type,variableInfo.name); 35 | return result.splitShader.prefix + code + result.splitShader.suffix; 36 | }; 37 | 38 | static GetVariableTypeAndName:(depthData:ShaderDepthData,definitionData:DefinitionData) => VariableTypeAndName|false = (depthData:ShaderDepthData,definitionData:DefinitionData) => { 39 | const definitionRegRes = ShaderPeeperRegex.defineRegex.exec(depthData.src); 40 | if(definitionRegRes){ 41 | const type:VariableType = definitionRegRes[1] as VariableType; 42 | const name:string = definitionRegRes[2]; 43 | return{type,name}; 44 | } 45 | const substituteRegRes = ShaderPeeperRegex.substitutionRegex.exec(depthData.src); 46 | console.log(substituteRegRes); 47 | if(substituteRegRes){ 48 | const name:string = substituteRegRes[1]; 49 | for(let i = depthData.depth; i >= 0; i--){ 50 | if(definitionData[i]){ 51 | if(definitionData[i][name]){ 52 | const type:VariableType = definitionData[i][name]; 53 | console.log(type,name); 54 | return{type,name}; 55 | } 56 | } 57 | } 58 | } 59 | return false; 60 | }; 61 | 62 | static GenerateSuffixCode:(variableType:VariableType,variableName:string) => string = (variableType:VariableType,variableName:string) => { 63 | switch (variableType) { 64 | case "int": 65 | return `gl_FragColor.xyz = vec3(${variableName});`; 66 | break; 67 | case "float": 68 | return `gl_FragColor = vec4(${variableName},0.0,0.0,1.0);`; 69 | break; 70 | case "vec2": 71 | return `gl_FragColor = vec4(${variableName},0.0,1.0);`; 72 | break; 73 | case "vec3": 74 | return `gl_FragColor = vec4(${variableName},1.0);`; 75 | break; 76 | case "vec4": 77 | return `gl_FragColor = vec4(${variableName});`; 78 | break; 79 | } 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /src/ShaderPeeper_Core/Controller/ICodeAnalyzer.ts: -------------------------------------------------------------------------------- 1 | export interface ICodeAnalyzer { 2 | Analyze:any; 3 | 4 | } 5 | -------------------------------------------------------------------------------- /src/ShaderPeeper_Core/Data/CursorPos.ts: -------------------------------------------------------------------------------- 1 | export interface CursorPos { 2 | row:number, 3 | column:number 4 | } 5 | -------------------------------------------------------------------------------- /src/ShaderPeeper_Core/Data/DefinitionData.ts: -------------------------------------------------------------------------------- 1 | import {VariableType} from "./VariableType"; 2 | 3 | export type DefinitionList = {[key:string]:VariableType}; 4 | export type DefinitionData = {[key:number]:DefinitionList}; 5 | -------------------------------------------------------------------------------- /src/ShaderPeeper_Core/Data/PeepAnalyzeResult.ts: -------------------------------------------------------------------------------- 1 | import {CursorPos} from "./CursorPos"; 2 | import {VariableType} from "./VariableType"; 3 | import {ShaderDepthData, ShaderDepthMap} from "./ShaderDepthData"; 4 | import {DefinitionData} from "./DefinitionData"; 5 | 6 | export type SplitShader = { 7 | prefix:string, 8 | mainFunc:string, 9 | suffix:string 10 | } 11 | 12 | export type PeepAnalyzeResult = { 13 | src:string; 14 | splitShader:SplitShader; 15 | cursorPos:CursorPos; 16 | index:number; 17 | depthMap:ShaderDepthMap; 18 | depthMapIndex:number; 19 | variableType:VariableType; 20 | definitionData:DefinitionData; 21 | } 22 | -------------------------------------------------------------------------------- /src/ShaderPeeper_Core/Data/ShaderDepthData.ts: -------------------------------------------------------------------------------- 1 | export type ShaderDepthData = {src:string,depth:number} 2 | export type ShaderDepthMap = Array; 3 | 4 | -------------------------------------------------------------------------------- /src/ShaderPeeper_Core/Data/ShaderPeeperRegex.ts: -------------------------------------------------------------------------------- 1 | export class ShaderPeeperRegex{ 2 | static spaceRegex = new RegExp("\\s|\\t|\\n"); 3 | static typeRegex = new RegExp("int|float|vec2|vec3"); 4 | static equalRegex = new RegExp("(?:\\+=|-=|\\*=|/=|=)") 5 | static defineRegex = new RegExp(`(${ShaderPeeperRegex.typeRegex.source})(?:${ShaderPeeperRegex.spaceRegex.source}+?)((?:\\w|_)+)(?:${ShaderPeeperRegex.spaceRegex.source}*?)=`); 6 | static substitutionRegex = new RegExp(`((?:\\w|_)+)(?:|\\.\\w+)(?:${ShaderPeeperRegex.spaceRegex.source}*?)${ShaderPeeperRegex.equalRegex.source}`); 7 | static mainPartRegex = new RegExp(`void(?:${ShaderPeeperRegex.spaceRegex.source}+?)main(?:${ShaderPeeperRegex.spaceRegex.source}*?)\\(.*?\\)(?:${ShaderPeeperRegex.spaceRegex.source}*?){`); 8 | } 9 | -------------------------------------------------------------------------------- /src/ShaderPeeper_Core/Data/VariableType.ts: -------------------------------------------------------------------------------- 1 | export type VariableType = "int"|"float"|"vec2"|"vec3"|"vec4"; 2 | 3 | -------------------------------------------------------------------------------- /src/ShaderPeeper_Test/analyze.test.ts: -------------------------------------------------------------------------------- 1 | import {GLSLPeepAnalyzer} from "../ShaderPeeper_Core/Controller/GLSLPeepAnalyzer"; 2 | import {GLSLPeepCodeGenerator} from "../ShaderPeeper_Core/Controller/GLSLPeepCodeGenerator"; 3 | import {ShaderPeeperRegex} from "../ShaderPeeper_Core/Data/ShaderPeeperRegex"; 4 | 5 | test("analyze test",()=>{ 6 | const code = 7 | ` 8 | vec3 col = vec3(uv,1.0); 9 | {vec2 col2 = vec2(0.5,1.0); 10 | float hoge = 0.0025;} 11 | float huga = 0.0125; 12 | `; 13 | const a = GLSLPeepAnalyzer.Analyze(code,{row:2,column:6}); 14 | console.log(a); 15 | if(a){ 16 | expect(GLSLPeepCodeGenerator.Generate(a)).toBe(code); 17 | } 18 | const codeB = ` 19 | precision highp float; 20 | 21 | uniform vec3 resolution; 22 | uniform float time; 23 | 24 | void main(void) { 25 | vec2 uv = (gl_FragCoord.xy * 2.0 - resolution.xy) / min(resolution.x, resolution.y); 26 | uv.xy += sin(uv + time) * 0.5 + 0.5; 27 | gl_FragColor = vec4(uv, cos(time) * 0.5 + 0.5, 1.0); 28 | } 29 | `; 30 | const b = GLSLPeepAnalyzer.Analyze(codeB,{row:8,column:10}); 31 | console.log(b); 32 | if(b){ 33 | console.log(GLSLPeepCodeGenerator.Generate(b)); 34 | expect(GLSLPeepCodeGenerator.Generate(b)).toBe(codeB); 35 | } 36 | 37 | }); 38 | -------------------------------------------------------------------------------- /src/ShaderPeeper_Test/sample.test.ts: -------------------------------------------------------------------------------- 1 | test("sample test",() => { 2 | expect(1+2).toBe(3); 3 | }); 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./build", /* Redirect output structure to the directory. */ 18 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 43 | 44 | /* Module Resolution Options */ 45 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 46 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 47 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 48 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 49 | // "typeRoots": [], /* List of folders to include type definitions from. */ 50 | // "types": [], /* Type declaration files to be included in compilation. */ 51 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 52 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 54 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 55 | 56 | /* Source Map Options */ 57 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 59 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 60 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 61 | 62 | /* Experimental Options */ 63 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 64 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 65 | 66 | /* Advanced Options */ 67 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 68 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 69 | }, 70 | "exclude": ["babel.config.js"] 71 | } 72 | --------------------------------------------------------------------------------