From aefafb4f9d40db9cb2d4ec4617501291247d4e1d Mon Sep 17 00:00:00 2001 From: roguib Date: Sun, 19 Sep 2021 20:09:20 +0200 Subject: [PATCH 01/12] move RegEx selector to their own file --- src/parser.ts | 83 ++++++++++++++----------------------- src/utils/regexSelectors.ts | 39 +++++++++++++++++ 2 files changed, 70 insertions(+), 52 deletions(-) create mode 100644 src/utils/regexSelectors.ts diff --git a/src/parser.ts b/src/parser.ts index 9a375c4..2a75975 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -4,35 +4,8 @@ const path = require("path"); import { File, Input, Output } from "./shared/IFile"; import logger from "./shared/logger"; import pathResolver from "./utils/path-resolver"; +import { REGEX_SELECTORS } from "./utils/regexSelectors"; -// selectors -const componentSelector = /export(\s+)class(\s+)[a-zA-Z0-9-_]+/g; -const componentHTMLselector = /selector:(\s+)(\"|')[a-zA-Z-_]+(\"|')/g; - -// inputs -// @Input() variableName; and @Input() variableName -const regularInputSelector = /@Input\(\)(\s+)[a-zA-Z0-9-_]+(;|)/g; -// @Input() variableName: type; and @Input() variableName: number = 9; -const regularInputWithTypeSelector = /@Input\(\)(\s+)[a-zA-Z0-9-_]+:(\s+)[a-zA-Z0-9-_]+((;|)|(\s+)[a-zA-Z0-9-_]+(\s+)=(\s+)[a-zA-Z0-9-_]+(;|))/g; -// @Input('inputName') varName: type; and @Input("inputName") varName: type -const customNameInputWithTypeSelector = /@Input\(('|")[a-zA-Z0-9-_]+('|")\)(\s+)[a-zA-Z0-9-_]+:(\s+)[a-zA-Z0-9-_]+\;/g; -const regularInputLiteralTypeSelector = /@Input\(\)(\s+)[a-zA-Z0-9-_]+:((\s+)(('|")[a-zA-Z0-9-_]+('|")((\s+)\|)))+(\s+)('|")[a-zA-Z0-9-_]+('|")(;|:|)/g; -//@Input() set foo(value) {} -const setterInputSelector = /@Input\(\)(\s+)set(\s+)[a-zA-Z0-9-_]+\([a-zA-Z0-9-_]+\)(\s+)/g; -//@Input() set foo(value: type) {} -const setterInputWithTypeSelector = /@Input\(\)(\s+)set(\s+)[a-zA-Z0-9-_]+\([a-zA-Z0-9-_]+:(\s+)[a-zA-Z0-9-_]+\)(\s+)/g; -// @Input('inputNameC') varName = 'adv'; -const setterInputCustomNameSelector = /@Input\(("|')[a-zA-Z0-9-_]+("|')\)(\s+)[a-zA-Z0-9-_]+(\s+)=(\s+)[A-Za-z0-9"']+(;|)/g; -//@Input() set foo(value: 'type1' | 'type2') {} -const setterInputLiteralTypeSelector = /@Input\(\)(\s+)set(\s+)[a-zA-Z0-9-_]+\([a-zA-Z0-9-_]+:((\s+)('|")[a-zA-Z0-9-_]+('|")(\s+)\|)+(\s)('|")[a-zA-Z0-9-_]+('|")\)/g; - -// outputs -// @Output() buttonClick: EventEmitter = new EventEmitter() -const regularOutputSelector = /@Output\(\)(\s+)[a-zA-Z0-9-_]+:(\s+)EventEmitter<[a-zA-Z0-9-_]+>(\s+)=(\s+)new(\s+)EventEmitter\(\);/g; - -// other -const extendedClassSelector = /export(\s+)class(\s+)[a-zA-Z0-9-_]+(\s+)extends(\s+)[a-zA-Z0-9-_]+/g; -const extendedClassPathSelector = /import(\s+){(\s+)[a-zA-Z0-9-_]+(\s+)}(\s+)from(\s+)[\/\@A-Za-z0-9."'_-]+/g; // TODO: class implementation with inputs/outputs defined // TODO: Test in other OS (github actions) @@ -62,7 +35,8 @@ export const parser = (filePaths: Array): Array => { continue; } - let fileNameData: Array = file?.match(componentSelector) || []; + let fileNameData: Array = + file?.match(REGEX_SELECTORS.componentSelector) || []; if (fileNameData.length === 0) { logger.warn("Component tag not defined by any class."); continue; @@ -77,7 +51,7 @@ export const parser = (filePaths: Array): Array => { if (containsComponentDef) { // match returns a string not an array let componentSelectorData: Array = - file?.match(componentHTMLselector) || []; + file?.match(REGEX_SELECTORS.componentHTMLselector) || []; if (componentSelectorData.length === 0) { logger.warn( "Component doesn't define any selector but contains @Component anotation." @@ -93,7 +67,7 @@ export const parser = (filePaths: Array): Array => { // Input() foo: 'type1' | 'type2' let inputs: Array = []; let inputsData: Array = - file?.match(regularInputLiteralTypeSelector) || []; + file?.match(REGEX_SELECTORS.regularInputLiteralTypeSelector) || []; for (let input of inputsData) { logger.log("inputs parsed:", inputsData); let tmp: Array = input.replace(/(\s)+/g, " ").split(" "); @@ -108,11 +82,12 @@ export const parser = (filePaths: Array): Array => { type, }); } - file = file.replace(regularInputLiteralTypeSelector, ""); + file = file.replace(REGEX_SELECTORS.regularInputLiteralTypeSelector, ""); // @Input() variableName: type; and @Input() variableName: number = 9; inputsData = []; - inputsData = file?.match(regularInputWithTypeSelector) || []; + inputsData = + file?.match(REGEX_SELECTORS.regularInputWithTypeSelector) || []; for (let input of inputsData) { let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); inputs.push({ @@ -120,11 +95,12 @@ export const parser = (filePaths: Array): Array => { type: tmp[2].replace(";", ""), }); } - file = file.replace(regularInputWithTypeSelector, ""); + file = file.replace(REGEX_SELECTORS.regularInputWithTypeSelector, ""); inputsData = []; // @Input('inputName') varName: type; and @Input("inputName") varName: type - inputsData = file?.match(customNameInputWithTypeSelector) || []; + inputsData = + file?.match(REGEX_SELECTORS.customNameInputWithTypeSelector) || []; for (let input of inputsData) { let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); const inputName = (tmp[0].match(/('|")[a-zA-Z0-9-_]+('|")/g) || @@ -134,12 +110,13 @@ export const parser = (filePaths: Array): Array => { type: tmp[2].replace(";", ""), }); } - file = file.replace(customNameInputWithTypeSelector, ""); + file = file.replace(REGEX_SELECTORS.customNameInputWithTypeSelector, ""); // @Input('inputNameC') varName = 'adv'; // @Input("inputNameD") varName = 2354; inputsData = []; - inputsData = file?.match(setterInputCustomNameSelector) || []; + inputsData = + file?.match(REGEX_SELECTORS.setterInputCustomNameSelector) || []; for (let input of inputsData) { let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); const inputName = (tmp[0].match(/('|")[a-zA-Z0-9-_]+('|")/g) || [ @@ -150,11 +127,11 @@ export const parser = (filePaths: Array): Array => { type: undefined, }); } - file = file.replace(setterInputCustomNameSelector, ""); + file = file.replace(REGEX_SELECTORS.setterInputCustomNameSelector, ""); //@Input() set foo(value) {} inputsData = []; - inputsData = file?.match(setterInputSelector) || []; + inputsData = file?.match(REGEX_SELECTORS.setterInputSelector) || []; for (let input of inputsData) { let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); const inputName = tmp[2].replace(/(\s+)/g, "").split("(")[0]; @@ -163,11 +140,11 @@ export const parser = (filePaths: Array): Array => { type: undefined, }); } - file = file.replace(setterInputSelector, ""); + file = file.replace(REGEX_SELECTORS.setterInputSelector, ""); //@Input() set foo(value: type) {} inputsData = []; - inputsData = file?.match(setterInputWithTypeSelector) || []; + inputsData = file?.match(REGEX_SELECTORS.setterInputWithTypeSelector) || []; for (let input of inputsData) { let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); const inputName = tmp[2].replace(/(\s+)/g, "").split("(")[0]; @@ -177,11 +154,12 @@ export const parser = (filePaths: Array): Array => { type, }); } - file = file.replace(setterInputWithTypeSelector, ""); + file = file.replace(REGEX_SELECTORS.setterInputWithTypeSelector, ""); //@Input() set foo(value: 'type1' | 'type2') {} inputsData = []; - inputsData = file?.match(setterInputLiteralTypeSelector) || []; + inputsData = + file?.match(REGEX_SELECTORS.setterInputLiteralTypeSelector) || []; for (let input of inputsData) { let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); const inputName = tmp[2].replace(/(\s+)/g, "").split("(")[0]; @@ -195,12 +173,12 @@ export const parser = (filePaths: Array): Array => { type, }); } - file = file.replace(setterInputLiteralTypeSelector, ""); + file = file.replace(REGEX_SELECTORS.setterInputLiteralTypeSelector, ""); // @Input() variableName; and @Input() variableName. Also for now we will parse // in this part of the code @Input() variableName = value and @Input() variableName = value; inputsData = []; - inputsData = file?.match(regularInputSelector) || []; + inputsData = file?.match(REGEX_SELECTORS.regularInputSelector) || []; for (let input of inputsData) { let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); const inputName = tmp[1].replace(";", ""); @@ -212,12 +190,13 @@ export const parser = (filePaths: Array): Array => { type: undefined, }); } - file = file.replace(regularInputSelector, ""); + file = file.replace(REGEX_SELECTORS.regularInputSelector, ""); logger.log("Inputs detected:", inputs); let outputs: Array = []; // only @Output() buttonClick: EventEmitter = new EventEmitter(); for now - let outputsData: Array = file?.match(regularOutputSelector) || []; + let outputsData: Array = + file?.match(REGEX_SELECTORS.regularOutputSelector) || []; for (let output of outputsData) { let tmp: Array = output.replace(/(\s+)/g, " ").split(" "); outputs.push({ @@ -228,27 +207,27 @@ export const parser = (filePaths: Array): Array => { .replace("<", ""), }); } - file = file.replace(regularOutputSelector, ""); + file = file.replace(REGEX_SELECTORS.regularOutputSelector, ""); logger.log("Outputs detected:", outputs); let extendedClassPath; - if (file?.match(extendedClassSelector)) { + if (file?.match(REGEX_SELECTORS.extendedClassSelector)) { // we should see if the extended class is in tmp and if not extract the inputs defined inside let matchExtendedClass: Array = - file?.match(extendedClassSelector) || []; + file?.match(REGEX_SELECTORS.extendedClassSelector) || []; // resolve the path of the class let extendedClass: string = matchExtendedClass[0] .replace(/(\s+)/g, " ") .split(" ")[4]; logger.log("extendedClassName:", extendedClass); let matchExtendedClassPath: Array = - file?.match(extendedClassPathSelector) || []; + file?.match(REGEX_SELECTORS.extendedClassPathSelector) || []; extendedClassPath = pathResolver.resolve( filePath, matchExtendedClassPath[0] ); - + logger.log("path:", extendedClassPath); } diff --git a/src/utils/regexSelectors.ts b/src/utils/regexSelectors.ts new file mode 100644 index 0000000..6abc03c --- /dev/null +++ b/src/utils/regexSelectors.ts @@ -0,0 +1,39 @@ +// selectors +export const REGEX_SELECTORS = { + componentSelector: /export(\s+)class(\s+)[a-zA-Z0-9-_]+/g, + componentHTMLselector: /selector:(\s+)(\"|')[a-zA-Z-_]+(\"|')/g, + // inputs + // @Input() variableName; and @Input() variableName + regularInputSelector: /@Input\(\)(\s+)[a-zA-Z0-9-_]+(;|)/g, + // @Input() variableName: type; and @Input() variableName: number = 9; + regularInputWithTypeSelector: + /@Input\(\)(\s+)[a-zA-Z0-9-_]+:(\s+)[a-zA-Z0-9-_]+((;|)|(\s+)[a-zA-Z0-9-_]+(\s+)=(\s+)[a-zA-Z0-9-_]+(;|))/g, + // @Input('inputName') varName: type; and @Input("inputName") varName: type + customNameInputWithTypeSelector: + /@Input\(('|")[a-zA-Z0-9-_]+('|")\)(\s+)[a-zA-Z0-9-_]+:(\s+)[a-zA-Z0-9-_]+\;/g, + regularInputLiteralTypeSelector: + /@Input\(\)(\s+)[a-zA-Z0-9-_]+:((\s+)(('|")[a-zA-Z0-9-_]+('|")((\s+)\|)))+(\s+)('|")[a-zA-Z0-9-_]+('|")(;|:|)/g, + //@Input() set foo(value) {} + setterInputSelector: + /@Input\(\)(\s+)set(\s+)[a-zA-Z0-9-_]+\([a-zA-Z0-9-_]+\)(\s+)/g, + //@Input() set foo(value: type) {} + setterInputWithTypeSelector: + /@Input\(\)(\s+)set(\s+)[a-zA-Z0-9-_]+\([a-zA-Z0-9-_]+:(\s+)[a-zA-Z0-9-_]+\)(\s+)/g, + // @Input('inputNameC') varName = 'adv'; + setterInputCustomNameSelector: + /@Input\(("|')[a-zA-Z0-9-_]+("|')\)(\s+)[a-zA-Z0-9-_]+(\s+)=(\s+)[A-Za-z0-9"']+(;|)/g, + //@Input() set foo(value: 'type1' | 'type2') {} + setterInputLiteralTypeSelector: + /@Input\(\)(\s+)set(\s+)[a-zA-Z0-9-_]+\([a-zA-Z0-9-_]+:((\s+)('|")[a-zA-Z0-9-_]+('|")(\s+)\|)+(\s)('|")[a-zA-Z0-9-_]+('|")\)/g, + + // outputs + // @Output() buttonClick: EventEmitter = new EventEmitter() + regularOutputSelector: + /@Output\(\)(\s+)[a-zA-Z0-9-_]+:(\s+)EventEmitter<[a-zA-Z0-9-_]+>(\s+)=(\s+)new(\s+)EventEmitter\(\);/g, + + // other + extendedClassSelector: + /export(\s+)class(\s+)[a-zA-Z0-9-_]+(\s+)extends(\s+)[a-zA-Z0-9-_]+/g, + extendedClassPathSelector: + /import(\s+){(\s+)[a-zA-Z0-9-_]+(\s+)}(\s+)from(\s+)[\/\@A-Za-z0-9."'_-]+/g, +}; From b73835c0d931902fb45922ddab830443623bd2e5 Mon Sep 17 00:00:00 2001 From: roguib Date: Tue, 21 Sep 2021 12:48:07 +0200 Subject: [PATCH 02/12] provisional FileType --- src/shared/constants.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/shared/constants.ts diff --git a/src/shared/constants.ts b/src/shared/constants.ts new file mode 100644 index 0000000..4fa46ad --- /dev/null +++ b/src/shared/constants.ts @@ -0,0 +1 @@ +export type FileType = "CLASS" | "COMPONENT"; From e33e13a9e747239d42666ffb65c18bf46b30d086 Mon Sep 17 00:00:00 2001 From: roguib Date: Tue, 21 Sep 2021 17:39:50 +0200 Subject: [PATCH 03/12] increased printWidth to make code more readable --- .prettierrc | 5 +++ src/generator.ts | 23 +++++------ src/index.ts | 5 +-- src/parser.ts | 78 ++++++++++--------------------------- src/shared/logger.ts | 11 +++--- src/utils/path-resolver.ts | 23 +++-------- src/utils/regexSelectors.ts | 27 +++++-------- src/walker.ts | 11 +----- tests/generator.test.ts | 57 +++++++++++++-------------- tests/index.test.ts | 2 +- tests/parser.test.ts | 48 +++++------------------ tests/walker.test.ts | 7 +--- 12 files changed, 94 insertions(+), 203 deletions(-) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..99ef3b7 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "tabWidth": 2, + "useTabs": false, + "printWidth": 160 +} diff --git a/src/generator.ts b/src/generator.ts index 108eec2..c58d49e 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -7,19 +7,16 @@ export const generator = (files: Array, outputPath: string): void => { // scope will be html only for now let json = Object(); for (const file of files) { - let component = Object(), inputs = "", index = 1; + let component = Object(), + inputs = "", + index = 1; component.scope = "html"; component.prefix = file.prefix; for (let input of file.inputs) { - if (input.type?.indexOf('|') != -1 && input.type) { - inputs += - ` [${input.inputName}]=\"$\{${index}\|${input.type.replace(/(\s)\|(\s)/g, ',') - .replace(/'/g, '')}\|\}\"`; - } - else { - inputs += input.type === "string" ? - ` ${input.inputName}=` : - ` [${input.inputName}]=`; + if (input.type?.indexOf("|") != -1 && input.type) { + inputs += ` [${input.inputName}]=\"$\{${index}\|${input.type.replace(/(\s)\|(\s)/g, ",").replace(/'/g, "")}\|\}\"`; + } else { + inputs += input.type === "string" ? ` ${input.inputName}=` : ` [${input.inputName}]=`; inputs += `\"$${index}\"`; } ++index; @@ -30,11 +27,9 @@ export const generator = (files: Array, outputPath: string): void => { outputs += ` (${output.outputName})=\"$${index}\"`; ++index; } - component.body = [ - `<${file.prefix}` + inputs + outputs + `>`, - ]; + component.body = [`<${file.prefix}` + inputs + outputs + `>`]; json[file.componentName] = { - ...component + ...component, }; } const dir = path.join(outputPath, "/out.code-snippets"); diff --git a/src/index.ts b/src/index.ts index eced47b..f585b92 100644 --- a/src/index.ts +++ b/src/index.ts @@ -65,10 +65,7 @@ export const run = async (args: string[]) => { process.env.ROOT_PROJECT_PATH = config.workingDir || path.posix.resolve(); - let candidateFilePaths: Array = walker.walker( - process.env.ROOT_PROJECT_PATH as string, - [] - ); + let candidateFilePaths: Array = walker.walker(process.env.ROOT_PROJECT_PATH as string, []); let fileData: Array = parser.parser(candidateFilePaths); generator.generator(fileData, config.outputDir as string); }; diff --git a/src/parser.ts b/src/parser.ts index 2a75975..79c5194 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -26,17 +26,12 @@ export const parser = (filePaths: Array): Array => { containsComponentDef = true; } - if ( - !containsComponentDef && - file?.match(/@Input/g) == null && - file?.match(/@Output/g) == null - ) { + if (!containsComponentDef && file?.match(/@Input/g) == null && file?.match(/@Output/g) == null) { logger.log("No component, Inputs or Outputs defined in this file"); continue; } - let fileNameData: Array = - file?.match(REGEX_SELECTORS.componentSelector) || []; + let fileNameData: Array = file?.match(REGEX_SELECTORS.componentSelector) || []; if (fileNameData.length === 0) { logger.warn("Component tag not defined by any class."); continue; @@ -50,12 +45,9 @@ export const parser = (filePaths: Array): Array => { let selector = ""; if (containsComponentDef) { // match returns a string not an array - let componentSelectorData: Array = - file?.match(REGEX_SELECTORS.componentHTMLselector) || []; + let componentSelectorData: Array = file?.match(REGEX_SELECTORS.componentHTMLselector) || []; if (componentSelectorData.length === 0) { - logger.warn( - "Component doesn't define any selector but contains @Component anotation." - ); + logger.warn("Component doesn't define any selector but contains @Component anotation."); continue; } componentSelectorData[0].replace(/(\s+)/g, " "); @@ -66,17 +58,11 @@ export const parser = (filePaths: Array): Array => { // notice we ignore the default value of the input in the regex // Input() foo: 'type1' | 'type2' let inputs: Array = []; - let inputsData: Array = - file?.match(REGEX_SELECTORS.regularInputLiteralTypeSelector) || []; + let inputsData: Array = file?.match(REGEX_SELECTORS.regularInputLiteralTypeSelector) || []; for (let input of inputsData) { logger.log("inputs parsed:", inputsData); let tmp: Array = input.replace(/(\s)+/g, " ").split(" "); - let type = tmp - .slice(2, tmp.length) - .join() - .replace(/\"/g, "'") - .replace(";", "") - .replace(/,/g, ""); + let type = tmp.slice(2, tmp.length).join().replace(/\"/g, "'").replace(";", "").replace(/,/g, ""); inputs.push({ inputName: tmp[1].replace(":", ""), type, @@ -86,8 +72,7 @@ export const parser = (filePaths: Array): Array => { // @Input() variableName: type; and @Input() variableName: number = 9; inputsData = []; - inputsData = - file?.match(REGEX_SELECTORS.regularInputWithTypeSelector) || []; + inputsData = file?.match(REGEX_SELECTORS.regularInputWithTypeSelector) || []; for (let input of inputsData) { let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); inputs.push({ @@ -99,12 +84,10 @@ export const parser = (filePaths: Array): Array => { inputsData = []; // @Input('inputName') varName: type; and @Input("inputName") varName: type - inputsData = - file?.match(REGEX_SELECTORS.customNameInputWithTypeSelector) || []; + inputsData = file?.match(REGEX_SELECTORS.customNameInputWithTypeSelector) || []; for (let input of inputsData) { let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); - const inputName = (tmp[0].match(/('|")[a-zA-Z0-9-_]+('|")/g) || - [])[0].replace(/'|"/g, ""); + const inputName = (tmp[0].match(/('|")[a-zA-Z0-9-_]+('|")/g) || [])[0].replace(/'|"/g, ""); inputs.push({ inputName, type: tmp[2].replace(";", ""), @@ -115,13 +98,10 @@ export const parser = (filePaths: Array): Array => { // @Input('inputNameC') varName = 'adv'; // @Input("inputNameD") varName = 2354; inputsData = []; - inputsData = - file?.match(REGEX_SELECTORS.setterInputCustomNameSelector) || []; + inputsData = file?.match(REGEX_SELECTORS.setterInputCustomNameSelector) || []; for (let input of inputsData) { let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); - const inputName = (tmp[0].match(/('|")[a-zA-Z0-9-_]+('|")/g) || [ - "", - ])[0].replace(/'|"/g, ""); + const inputName = (tmp[0].match(/('|")[a-zA-Z0-9-_]+('|")/g) || [""])[0].replace(/'|"/g, ""); inputs.push({ inputName, type: undefined, @@ -158,8 +138,7 @@ export const parser = (filePaths: Array): Array => { //@Input() set foo(value: 'type1' | 'type2') {} inputsData = []; - inputsData = - file?.match(REGEX_SELECTORS.setterInputLiteralTypeSelector) || []; + inputsData = file?.match(REGEX_SELECTORS.setterInputLiteralTypeSelector) || []; for (let input of inputsData) { let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); const inputName = tmp[2].replace(/(\s+)/g, "").split("(")[0]; @@ -195,16 +174,12 @@ export const parser = (filePaths: Array): Array => { let outputs: Array = []; // only @Output() buttonClick: EventEmitter = new EventEmitter(); for now - let outputsData: Array = - file?.match(REGEX_SELECTORS.regularOutputSelector) || []; + let outputsData: Array = file?.match(REGEX_SELECTORS.regularOutputSelector) || []; for (let output of outputsData) { let tmp: Array = output.replace(/(\s+)/g, " ").split(" "); outputs.push({ outputName: tmp[1].replace(":", ""), - type: tmp[2] - .substr(tmp[2].indexOf("<"), tmp[2].indexOf(">")) - .replace(">", "") - .replace("<", ""), + type: tmp[2].substr(tmp[2].indexOf("<"), tmp[2].indexOf(">")).replace(">", "").replace("<", ""), }); } file = file.replace(REGEX_SELECTORS.regularOutputSelector, ""); @@ -213,20 +188,13 @@ export const parser = (filePaths: Array): Array => { let extendedClassPath; if (file?.match(REGEX_SELECTORS.extendedClassSelector)) { // we should see if the extended class is in tmp and if not extract the inputs defined inside - let matchExtendedClass: Array = - file?.match(REGEX_SELECTORS.extendedClassSelector) || []; + let matchExtendedClass: Array = file?.match(REGEX_SELECTORS.extendedClassSelector) || []; // resolve the path of the class - let extendedClass: string = matchExtendedClass[0] - .replace(/(\s+)/g, " ") - .split(" ")[4]; + let extendedClass: string = matchExtendedClass[0].replace(/(\s+)/g, " ").split(" ")[4]; logger.log("extendedClassName:", extendedClass); - let matchExtendedClassPath: Array = - file?.match(REGEX_SELECTORS.extendedClassPathSelector) || []; + let matchExtendedClassPath: Array = file?.match(REGEX_SELECTORS.extendedClassPathSelector) || []; - extendedClassPath = pathResolver.resolve( - filePath, - matchExtendedClassPath[0] - ); + extendedClassPath = pathResolver.resolve(filePath, matchExtendedClassPath[0]); logger.log("path:", extendedClassPath); } @@ -264,14 +232,8 @@ export const parser = (filePaths: Array): Array => { if (result[i].extendedClassFilepath) { for (let j = 0; j < tmp.length; ++j) { if (result[i].extendedClassFilepath === tmp[j].fileLocation) { - result[i].inputs = [ - ...result[i].inputs, - ...(tmp[j].inputs as []), - ] as Input[]; - result[i].outputs = [ - ...result[i].outputs, - ...(tmp[j].outputs as []), - ] as Output[]; + result[i].inputs = [...result[i].inputs, ...(tmp[j].inputs as [])] as Input[]; + result[i].outputs = [...result[i].outputs, ...(tmp[j].outputs as [])] as Output[]; break; } } diff --git a/src/shared/logger.ts b/src/shared/logger.ts index ec2a7c5..2863b8d 100644 --- a/src/shared/logger.ts +++ b/src/shared/logger.ts @@ -12,26 +12,25 @@ let debuggerEnabled = false; export const enableDebugger = () => { debuggerEnabled = true; -} +}; export const disableDebugger = () => { debuggerEnabled = false; -} +}; export const log = (message: string, args?: any[] | any) => { - if (!debuggerEnabled) return; + if (!debuggerEnabled) return; console.log(logPrefix + message, args); }; export const warn = (message: string, args?: any[] | any) => { - if (!debuggerEnabled) return; + if (!debuggerEnabled) return; console.log(warnPrefix + message, args); }; export const err = (message: string, args?: any[] | any) => { - console.log(errorPrefix + message, args); process.exit(); }; @@ -41,4 +40,4 @@ export default { warn, err, enableDebugger, -}; \ No newline at end of file +}; diff --git a/src/utils/path-resolver.ts b/src/utils/path-resolver.ts index 4bc4fa4..86afd75 100644 --- a/src/utils/path-resolver.ts +++ b/src/utils/path-resolver.ts @@ -12,10 +12,7 @@ let tsconfigFile: { * @param {string} importExpr The expression used to import the base class * @returns {string} An absolute path to the imported file */ -export const resolve = ( - filePath: string, - importExpr: string -): string | null => { +export const resolve = (filePath: string, importExpr: string): string | null => { /** * @param {string} importExpr The expression used to import the class in the file * @returns {string} The the path in the import expression @@ -48,30 +45,20 @@ export const resolve = ( let compilerOptionsPathsKey = pathToFile.substr(0, pathToFile.indexOf("/")), compilerOptionsPathsValue: Array = [""]; if (compilerOptionsPathsKey + "/*" in tsconfigFile?.compilerOptions.paths) { - compilerOptionsPathsValue = - tsconfigFile?.compilerOptions.paths[compilerOptionsPathsKey + "/*"]; + compilerOptionsPathsValue = tsconfigFile?.compilerOptions.paths[compilerOptionsPathsKey + "/*"]; } // TODO: else throw an exception - compilerOptionsPathsValue[0] = compilerOptionsPathsValue[0].replace( - "/*", - "" - ); + compilerOptionsPathsValue[0] = compilerOptionsPathsValue[0].replace("/*", ""); // Notice that by calling path.join with a relative path of the base // path from ComponentClassPath and the full path of the file resolves into the // full path of the base path resolvedPath = path.join( path.posix.resolve(), - pathToFile - .replace(compilerOptionsPathsKey, compilerOptionsPathsValue[0]) - .replace(/(\s+)/g, " ") - .replace(/"/g, "") + ".ts" + pathToFile.replace(compilerOptionsPathsKey, compilerOptionsPathsValue[0]).replace(/(\s+)/g, " ").replace(/"/g, "") + ".ts" ); } else { - resolvedPath = path.join( - path.dirname(filePath), - pathToFile.replace(/(\s+)/g, " ").replace(/"/g, "") + ".ts" - ); + resolvedPath = path.join(path.dirname(filePath), pathToFile.replace(/(\s+)/g, " ").replace(/"/g, "") + ".ts"); } // TODO: Throw an error if the function is unable to resolve the path diff --git a/src/utils/regexSelectors.ts b/src/utils/regexSelectors.ts index 6abc03c..0a21823 100644 --- a/src/utils/regexSelectors.ts +++ b/src/utils/regexSelectors.ts @@ -6,34 +6,25 @@ export const REGEX_SELECTORS = { // @Input() variableName; and @Input() variableName regularInputSelector: /@Input\(\)(\s+)[a-zA-Z0-9-_]+(;|)/g, // @Input() variableName: type; and @Input() variableName: number = 9; - regularInputWithTypeSelector: - /@Input\(\)(\s+)[a-zA-Z0-9-_]+:(\s+)[a-zA-Z0-9-_]+((;|)|(\s+)[a-zA-Z0-9-_]+(\s+)=(\s+)[a-zA-Z0-9-_]+(;|))/g, + regularInputWithTypeSelector: /@Input\(\)(\s+)[a-zA-Z0-9-_]+:(\s+)[a-zA-Z0-9-_]+((;|)|(\s+)[a-zA-Z0-9-_]+(\s+)=(\s+)[a-zA-Z0-9-_]+(;|))/g, // @Input('inputName') varName: type; and @Input("inputName") varName: type - customNameInputWithTypeSelector: - /@Input\(('|")[a-zA-Z0-9-_]+('|")\)(\s+)[a-zA-Z0-9-_]+:(\s+)[a-zA-Z0-9-_]+\;/g, - regularInputLiteralTypeSelector: - /@Input\(\)(\s+)[a-zA-Z0-9-_]+:((\s+)(('|")[a-zA-Z0-9-_]+('|")((\s+)\|)))+(\s+)('|")[a-zA-Z0-9-_]+('|")(;|:|)/g, + customNameInputWithTypeSelector: /@Input\(('|")[a-zA-Z0-9-_]+('|")\)(\s+)[a-zA-Z0-9-_]+:(\s+)[a-zA-Z0-9-_]+\;/g, + regularInputLiteralTypeSelector: /@Input\(\)(\s+)[a-zA-Z0-9-_]+:((\s+)(('|")[a-zA-Z0-9-_]+('|")((\s+)\|)))+(\s+)('|")[a-zA-Z0-9-_]+('|")(;|:|)/g, //@Input() set foo(value) {} - setterInputSelector: - /@Input\(\)(\s+)set(\s+)[a-zA-Z0-9-_]+\([a-zA-Z0-9-_]+\)(\s+)/g, + setterInputSelector: /@Input\(\)(\s+)set(\s+)[a-zA-Z0-9-_]+\([a-zA-Z0-9-_]+\)(\s+)/g, //@Input() set foo(value: type) {} - setterInputWithTypeSelector: - /@Input\(\)(\s+)set(\s+)[a-zA-Z0-9-_]+\([a-zA-Z0-9-_]+:(\s+)[a-zA-Z0-9-_]+\)(\s+)/g, + setterInputWithTypeSelector: /@Input\(\)(\s+)set(\s+)[a-zA-Z0-9-_]+\([a-zA-Z0-9-_]+:(\s+)[a-zA-Z0-9-_]+\)(\s+)/g, // @Input('inputNameC') varName = 'adv'; - setterInputCustomNameSelector: - /@Input\(("|')[a-zA-Z0-9-_]+("|')\)(\s+)[a-zA-Z0-9-_]+(\s+)=(\s+)[A-Za-z0-9"']+(;|)/g, + setterInputCustomNameSelector: /@Input\(("|')[a-zA-Z0-9-_]+("|')\)(\s+)[a-zA-Z0-9-_]+(\s+)=(\s+)[A-Za-z0-9"']+(;|)/g, //@Input() set foo(value: 'type1' | 'type2') {} setterInputLiteralTypeSelector: /@Input\(\)(\s+)set(\s+)[a-zA-Z0-9-_]+\([a-zA-Z0-9-_]+:((\s+)('|")[a-zA-Z0-9-_]+('|")(\s+)\|)+(\s)('|")[a-zA-Z0-9-_]+('|")\)/g, // outputs // @Output() buttonClick: EventEmitter = new EventEmitter() - regularOutputSelector: - /@Output\(\)(\s+)[a-zA-Z0-9-_]+:(\s+)EventEmitter<[a-zA-Z0-9-_]+>(\s+)=(\s+)new(\s+)EventEmitter\(\);/g, + regularOutputSelector: /@Output\(\)(\s+)[a-zA-Z0-9-_]+:(\s+)EventEmitter<[a-zA-Z0-9-_]+>(\s+)=(\s+)new(\s+)EventEmitter\(\);/g, // other - extendedClassSelector: - /export(\s+)class(\s+)[a-zA-Z0-9-_]+(\s+)extends(\s+)[a-zA-Z0-9-_]+/g, - extendedClassPathSelector: - /import(\s+){(\s+)[a-zA-Z0-9-_]+(\s+)}(\s+)from(\s+)[\/\@A-Za-z0-9."'_-]+/g, + extendedClassSelector: /export(\s+)class(\s+)[a-zA-Z0-9-_]+(\s+)extends(\s+)[a-zA-Z0-9-_]+/g, + extendedClassPathSelector: /import(\s+){(\s+)[a-zA-Z0-9-_]+(\s+)}(\s+)from(\s+)[\/\@A-Za-z0-9."'_-]+/g, }; diff --git a/src/walker.ts b/src/walker.ts index 6887dda..2a68440 100644 --- a/src/walker.ts +++ b/src/walker.ts @@ -5,10 +5,7 @@ const fs = require("fs"); const path = require("path"); // TODO: Parse files synchronously -export const walker = ( - root: string, - filesExploredPath: Array -): Array => { +export const walker = (root: string, filesExploredPath: Array): Array => { let pendingDir: Array = [root]; while (pendingDir.length > 0) { @@ -25,11 +22,7 @@ export const walker = ( if (file.isFile() && file.name.endsWith(".ts")) { logger.log("Candidate file to contain component definition:", file.name); filesExploredPath.push(path.join(currentDir, file.name)); - } else if ( - file.isDirectory() && - file.name != "node_modules" && - file.name != ".git" - ) { + } else if (file.isDirectory() && file.name != "node_modules" && file.name != ".git") { pendingDir.push(path.join(currentDir, file.name)); } } diff --git a/tests/generator.test.ts b/tests/generator.test.ts index 2f4694d..f5f7a67 100644 --- a/tests/generator.test.ts +++ b/tests/generator.test.ts @@ -3,37 +3,32 @@ const fs = require("fs"); const path = require("path"); test("JSON file generation", async () => { - generator.generator([ - { - componentName: "MainComponent", - prefix: "app-main", - inputs: [ - { inputName: "appName", type: "MediaModel" }, - { inputName: "var", type: "'type1' | 'type2'" }, - { inputName: "foo", type: "TypeError" }, - { inputName: "fooStr", type: "string" }, - ], - outputs: [ - { outputName: "buttonClick", type: "any" }, - { outputName: "fooVar", type: "number" }, - ], - fileLocation: path.join( - path.posix.resolve(), - "/tests/fixtures/parser/main.component.ts" - ), - extendedClassFilepath: undefined - } - ], path.join(path.posix.resolve(), "/out")); - const expectedResult = '{"MainComponent": { "scope": "html", "prefix": "app-main", "body": [ "" ] } }'.replace( - /\s/g, - "" + generator.generator( + [ + { + componentName: "MainComponent", + prefix: "app-main", + inputs: [ + { inputName: "appName", type: "MediaModel" }, + { inputName: "var", type: "'type1' | 'type2'" }, + { inputName: "foo", type: "TypeError" }, + { inputName: "fooStr", type: "string" }, + ], + outputs: [ + { outputName: "buttonClick", type: "any" }, + { outputName: "fooVar", type: "number" }, + ], + fileLocation: path.join(path.posix.resolve(), "/tests/fixtures/parser/main.component.ts"), + extendedClassFilepath: undefined, + }, + ], + path.join(path.posix.resolve(), "/out") ); - const data = fs - .readFileSync( - path.join(path.posix.resolve(), "/out/out.code-snippets"), - "utf-8" - ) - .replace(/\s+/g, "") - .replace(/\\/g, ""); + const expectedResult = + '{"MainComponent": { "scope": "html", "prefix": "app-main", "body": [ "" ] } }'.replace( + /\s/g, + "" + ); + const data = fs.readFileSync(path.join(path.posix.resolve(), "/out/out.code-snippets"), "utf-8").replace(/\s+/g, "").replace(/\\/g, ""); expect(data).toEqual(expectedResult); }); diff --git a/tests/index.test.ts b/tests/index.test.ts index 0a16199..4813289 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -3,5 +3,5 @@ const fs = require("fs"); const path = require("path"); test("End to end test", async () => { - // TODO + // TODO }); diff --git a/tests/parser.test.ts b/tests/parser.test.ts index 0821ac1..18cbbe7 100644 --- a/tests/parser.test.ts +++ b/tests/parser.test.ts @@ -5,14 +5,8 @@ test("Parses the contents of the candidate files and returns an array of File ty const result = [ { componentName: "MainComponent", - extendedClassFilepath: path.join( - path.posix.resolve(), - "/tests/fixtures/parser/base.component.ts" - ), - fileLocation: path.join( - path.posix.resolve(), - "/tests/fixtures/parser/main.component.ts" - ), + extendedClassFilepath: path.join(path.posix.resolve(), "/tests/fixtures/parser/base.component.ts"), + fileLocation: path.join(path.posix.resolve(), "/tests/fixtures/parser/main.component.ts"), inputs: [ { inputName: "literalType1", @@ -102,36 +96,20 @@ test("Parses the contents of the candidate files and returns an array of File ty ]; expect( parser.parser([ - path.join( - path.posix.resolve(), - "/tests/fixtures/parser/main.component.ts" - ), - path.join( - path.posix.resolve(), - "/tests/fixtures/parser/base.component.ts" - ), - path.join( - path.posix.resolve(), - "/tests/fixtures/parser/component.module.ts" - ), + path.join(path.posix.resolve(), "/tests/fixtures/parser/main.component.ts"), + path.join(path.posix.resolve(), "/tests/fixtures/parser/base.component.ts"), + path.join(path.posix.resolve(), "/tests/fixtures/parser/component.module.ts"), ]) ).toStrictEqual(result); }); test("Tests the parser when de file is imported using the @ special keyword path defined in tsconfig.json", async () => { - process.env.ROOT_PROJECT_PATH = - "C:/Users/roger/oos/angular-vs-snippets/tests/fixtures/parser"; + process.env.ROOT_PROJECT_PATH = "C:/Users/roger/oos/angular-vs-snippets/tests/fixtures/parser"; const result = [ { componentName: "SpecialPathComponent", - extendedClassFilepath: path.join( - path.posix.resolve(), - "/tests/fixtures/parser/special-path-tsconfig/special-base.component.ts" - ), - fileLocation: path.join( - path.posix.resolve(), - "/tests/fixtures/parser/special-path-tsconfig/special-path.component.ts" - ), + extendedClassFilepath: path.join(path.posix.resolve(), "/tests/fixtures/parser/special-path-tsconfig/special-base.component.ts"), + fileLocation: path.join(path.posix.resolve(), "/tests/fixtures/parser/special-path-tsconfig/special-path.component.ts"), inputs: [ { inputName: "inputInChildClass", @@ -148,14 +126,8 @@ test("Tests the parser when de file is imported using the @ special keyword path ]; expect( parser.parser([ - path.join( - path.posix.resolve(), - "/tests/fixtures/parser/special-path-tsconfig/special-base.component.ts" - ), - path.join( - path.posix.resolve(), - "/tests/fixtures/parser/special-path-tsconfig/special-path.component.ts" - ), + path.join(path.posix.resolve(), "/tests/fixtures/parser/special-path-tsconfig/special-base.component.ts"), + path.join(path.posix.resolve(), "/tests/fixtures/parser/special-path-tsconfig/special-path.component.ts"), ]) ).toStrictEqual(result); }); diff --git a/tests/walker.test.ts b/tests/walker.test.ts index d7eeb4b..8767225 100644 --- a/tests/walker.test.ts +++ b/tests/walker.test.ts @@ -3,12 +3,7 @@ import * as walker from "../src/walker"; const path = require("path"); test("Inspects /tests/fixtures/ts-files with walker function", () => { - expect( - walker.walker( - path.join(path.posix.resolve(), "tests/fixtures/ts-files"), - [] - ) - ).toStrictEqual([ + expect(walker.walker(path.join(path.posix.resolve(), "tests/fixtures/ts-files"), [])).toStrictEqual([ path.join(path.posix.resolve(), "/tests/fixtures/ts-files/foo.ts"), path.join(path.posix.resolve(), "/tests/fixtures/ts-files/index.ts"), ]); From 9f5e5728077056079c543b1707cb492f2cee9c24 Mon Sep 17 00:00:00 2001 From: roguib Date: Tue, 21 Sep 2021 17:46:53 +0200 Subject: [PATCH 04/12] move input parsing into its own function --- src/parser.ts | 237 ++++++++++++++++++++++++++------------------------ 1 file changed, 121 insertions(+), 116 deletions(-) diff --git a/src/parser.ts b/src/parser.ts index 79c5194..3882093 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -55,122 +55,7 @@ export const parser = (filePaths: Array): Array => { logger.log("Selector:", selector); } - // notice we ignore the default value of the input in the regex - // Input() foo: 'type1' | 'type2' - let inputs: Array = []; - let inputsData: Array = file?.match(REGEX_SELECTORS.regularInputLiteralTypeSelector) || []; - for (let input of inputsData) { - logger.log("inputs parsed:", inputsData); - let tmp: Array = input.replace(/(\s)+/g, " ").split(" "); - let type = tmp.slice(2, tmp.length).join().replace(/\"/g, "'").replace(";", "").replace(/,/g, ""); - inputs.push({ - inputName: tmp[1].replace(":", ""), - type, - }); - } - file = file.replace(REGEX_SELECTORS.regularInputLiteralTypeSelector, ""); - - // @Input() variableName: type; and @Input() variableName: number = 9; - inputsData = []; - inputsData = file?.match(REGEX_SELECTORS.regularInputWithTypeSelector) || []; - for (let input of inputsData) { - let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); - inputs.push({ - inputName: tmp[1].replace(":", ""), - type: tmp[2].replace(";", ""), - }); - } - file = file.replace(REGEX_SELECTORS.regularInputWithTypeSelector, ""); - - inputsData = []; - // @Input('inputName') varName: type; and @Input("inputName") varName: type - inputsData = file?.match(REGEX_SELECTORS.customNameInputWithTypeSelector) || []; - for (let input of inputsData) { - let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); - const inputName = (tmp[0].match(/('|")[a-zA-Z0-9-_]+('|")/g) || [])[0].replace(/'|"/g, ""); - inputs.push({ - inputName, - type: tmp[2].replace(";", ""), - }); - } - file = file.replace(REGEX_SELECTORS.customNameInputWithTypeSelector, ""); - - // @Input('inputNameC') varName = 'adv'; - // @Input("inputNameD") varName = 2354; - inputsData = []; - inputsData = file?.match(REGEX_SELECTORS.setterInputCustomNameSelector) || []; - for (let input of inputsData) { - let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); - const inputName = (tmp[0].match(/('|")[a-zA-Z0-9-_]+('|")/g) || [""])[0].replace(/'|"/g, ""); - inputs.push({ - inputName, - type: undefined, - }); - } - file = file.replace(REGEX_SELECTORS.setterInputCustomNameSelector, ""); - - //@Input() set foo(value) {} - inputsData = []; - inputsData = file?.match(REGEX_SELECTORS.setterInputSelector) || []; - for (let input of inputsData) { - let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); - const inputName = tmp[2].replace(/(\s+)/g, "").split("(")[0]; - inputs.push({ - inputName, - type: undefined, - }); - } - file = file.replace(REGEX_SELECTORS.setterInputSelector, ""); - - //@Input() set foo(value: type) {} - inputsData = []; - inputsData = file?.match(REGEX_SELECTORS.setterInputWithTypeSelector) || []; - for (let input of inputsData) { - let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); - const inputName = tmp[2].replace(/(\s+)/g, "").split("(")[0]; - const type = tmp[3].replace(/(\s+)/g, "").split(")")[0]; - inputs.push({ - inputName, - type, - }); - } - file = file.replace(REGEX_SELECTORS.setterInputWithTypeSelector, ""); - - //@Input() set foo(value: 'type1' | 'type2') {} - inputsData = []; - inputsData = file?.match(REGEX_SELECTORS.setterInputLiteralTypeSelector) || []; - for (let input of inputsData) { - let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); - const inputName = tmp[2].replace(/(\s+)/g, "").split("(")[0]; - const type = tmp - .slice(3, tmp.length) - .join() - .replace(/'|"|\)/g, "") - .replace(/,/g, " "); - inputs.push({ - inputName, - type, - }); - } - file = file.replace(REGEX_SELECTORS.setterInputLiteralTypeSelector, ""); - - // @Input() variableName; and @Input() variableName. Also for now we will parse - // in this part of the code @Input() variableName = value and @Input() variableName = value; - inputsData = []; - inputsData = file?.match(REGEX_SELECTORS.regularInputSelector) || []; - for (let input of inputsData) { - let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); - const inputName = tmp[1].replace(";", ""); - if (inputs.filter((elem) => elem.inputName == inputName).length > 0) { - continue; - } - inputs.push({ - inputName, - type: undefined, - }); - } - file = file.replace(REGEX_SELECTORS.regularInputSelector, ""); - logger.log("Inputs detected:", inputs); + let inputs: Array = parseInputs(file); let outputs: Array = []; // only @Output() buttonClick: EventEmitter = new EventEmitter(); for now @@ -241,3 +126,123 @@ export const parser = (filePaths: Array): Array => { } return result; }; + +const parseInputs = (file: string): Array => { + // notice we ignore the default value of the input in the regex + // Input() foo: 'type1' | 'type2' + let inputsData: Array = file?.match(REGEX_SELECTORS.regularInputLiteralTypeSelector) || [], + inputs: Array = []; + for (let input of inputsData) { + logger.log("inputs parsed:", inputsData); + let tmp: Array = input.replace(/(\s)+/g, " ").split(" "); + let type = tmp.slice(2, tmp.length).join().replace(/\"/g, "'").replace(";", "").replace(/,/g, ""); + inputs.push({ + inputName: tmp[1].replace(":", ""), + type, + }); + } + file = file.replace(REGEX_SELECTORS.regularInputLiteralTypeSelector, ""); + + // @Input() variableName: type; and @Input() variableName: number = 9; + inputsData = []; + inputsData = file?.match(REGEX_SELECTORS.regularInputWithTypeSelector) || []; + for (let input of inputsData) { + let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); + inputs.push({ + inputName: tmp[1].replace(":", ""), + type: tmp[2].replace(";", ""), + }); + } + file = file.replace(REGEX_SELECTORS.regularInputWithTypeSelector, ""); + + inputsData = []; + // @Input('inputName') varName: type; and @Input("inputName") varName: type + inputsData = file?.match(REGEX_SELECTORS.customNameInputWithTypeSelector) || []; + for (let input of inputsData) { + let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); + const inputName = (tmp[0].match(/('|")[a-zA-Z0-9-_]+('|")/g) || [])[0].replace(/'|"/g, ""); + inputs.push({ + inputName, + type: tmp[2].replace(";", ""), + }); + } + file = file.replace(REGEX_SELECTORS.customNameInputWithTypeSelector, ""); + + // @Input('inputNameC') varName = 'adv'; + // @Input("inputNameD") varName = 2354; + inputsData = []; + inputsData = file?.match(REGEX_SELECTORS.setterInputCustomNameSelector) || []; + for (let input of inputsData) { + let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); + const inputName = (tmp[0].match(/('|")[a-zA-Z0-9-_]+('|")/g) || [""])[0].replace(/'|"/g, ""); + inputs.push({ + inputName, + type: undefined, + }); + } + file = file.replace(REGEX_SELECTORS.setterInputCustomNameSelector, ""); + + //@Input() set foo(value) {} + inputsData = []; + inputsData = file?.match(REGEX_SELECTORS.setterInputSelector) || []; + for (let input of inputsData) { + let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); + const inputName = tmp[2].replace(/(\s+)/g, "").split("(")[0]; + inputs.push({ + inputName, + type: undefined, + }); + } + file = file.replace(REGEX_SELECTORS.setterInputSelector, ""); + + //@Input() set foo(value: type) {} + inputsData = []; + inputsData = file?.match(REGEX_SELECTORS.setterInputWithTypeSelector) || []; + for (let input of inputsData) { + let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); + const inputName = tmp[2].replace(/(\s+)/g, "").split("(")[0]; + const type = tmp[3].replace(/(\s+)/g, "").split(")")[0]; + inputs.push({ + inputName, + type, + }); + } + file = file.replace(REGEX_SELECTORS.setterInputWithTypeSelector, ""); + + //@Input() set foo(value: 'type1' | 'type2') {} + inputsData = []; + inputsData = file?.match(REGEX_SELECTORS.setterInputLiteralTypeSelector) || []; + for (let input of inputsData) { + let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); + const inputName = tmp[2].replace(/(\s+)/g, "").split("(")[0]; + const type = tmp + .slice(3, tmp.length) + .join() + .replace(/'|"|\)/g, "") + .replace(/,/g, " "); + inputs.push({ + inputName, + type, + }); + } + file = file.replace(REGEX_SELECTORS.setterInputLiteralTypeSelector, ""); + + // @Input() variableName; and @Input() variableName. Also for now we will parse + // in this part of the code @Input() variableName = value and @Input() variableName = value; + inputsData = []; + inputsData = file?.match(REGEX_SELECTORS.regularInputSelector) || []; + for (let input of inputsData) { + let tmp: Array = input.replace(/(\s+)/g, " ").split(" "); + const inputName = tmp[1].replace(";", ""); + if (inputs.filter((elem) => elem.inputName == inputName).length > 0) { + continue; + } + inputs.push({ + inputName, + type: undefined, + }); + } + file = file.replace(REGEX_SELECTORS.regularInputSelector, ""); + logger.log("Inputs detected:", inputs); + return inputs; +}; From ad780b8f14b174a619e137ee7a8b3434927d5ea5 Mon Sep 17 00:00:00 2001 From: roguib Date: Tue, 21 Sep 2021 17:56:42 +0200 Subject: [PATCH 05/12] move output parsing into its own function --- src/parser.ts | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/src/parser.ts b/src/parser.ts index 3882093..e392fe6 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -55,20 +55,8 @@ export const parser = (filePaths: Array): Array => { logger.log("Selector:", selector); } - let inputs: Array = parseInputs(file); - - let outputs: Array = []; - // only @Output() buttonClick: EventEmitter = new EventEmitter(); for now - let outputsData: Array = file?.match(REGEX_SELECTORS.regularOutputSelector) || []; - for (let output of outputsData) { - let tmp: Array = output.replace(/(\s+)/g, " ").split(" "); - outputs.push({ - outputName: tmp[1].replace(":", ""), - type: tmp[2].substr(tmp[2].indexOf("<"), tmp[2].indexOf(">")).replace(">", "").replace("<", ""), - }); - } - file = file.replace(REGEX_SELECTORS.regularOutputSelector, ""); - logger.log("Outputs detected:", outputs); + let inputs: Array = parseInputs(file), + outputs: Array = parseOutputs(file); let extendedClassPath; if (file?.match(REGEX_SELECTORS.extendedClassSelector)) { @@ -127,6 +115,11 @@ export const parser = (filePaths: Array): Array => { return result; }; +/** + * @private + * @param {string} file a string where to look for input definitions + * @returns {Array} An array containing all the inputs defined in the given string + */ const parseInputs = (file: string): Array => { // notice we ignore the default value of the input in the regex // Input() foo: 'type1' | 'type2' @@ -246,3 +239,25 @@ const parseInputs = (file: string): Array => { logger.log("Inputs detected:", inputs); return inputs; }; + +/** + * @private + * @param {string} file a string where to look for output definitions + * @returns {Array} An array containing all the outputs defined in the given string + */ +const parseOutputs = (file: string): Array => { + let outputs: Array = []; + // only @Output() buttonClick: EventEmitter = new EventEmitter(); for now + let outputsData: Array = file?.match(REGEX_SELECTORS.regularOutputSelector) || []; + for (let output of outputsData) { + let tmp: Array = output.replace(/(\s+)/g, " ").split(" "); + outputs.push({ + outputName: tmp[1].replace(":", ""), + type: tmp[2].substr(tmp[2].indexOf("<"), tmp[2].indexOf(">")).replace(">", "").replace("<", ""), + }); + } + file = file.replace(REGEX_SELECTORS.regularOutputSelector, ""); + logger.log("Outputs detected:", outputs); + + return outputs; +}; From a1f43f355baa8896086d32a567063bf47920cd39 Mon Sep 17 00:00:00 2001 From: roguib Date: Tue, 21 Sep 2021 18:35:20 +0200 Subject: [PATCH 06/12] walker -> reader --- DESIGN.md | 4 +-- src/index.ts | 5 ++-- src/reader.ts | 49 ++++++++++++++++++++++++++++++++ src/walker.ts | 31 -------------------- tests/fixtures/ts-files/foo.ts | 5 ++++ tests/fixtures/ts-files/index.ts | 0 tests/reader.test.ts | 17 +++++++++++ tests/walker.test.ts | 10 ------- 8 files changed, 76 insertions(+), 45 deletions(-) create mode 100644 src/reader.ts delete mode 100644 src/walker.ts delete mode 100644 tests/fixtures/ts-files/index.ts create mode 100644 tests/reader.test.ts delete mode 100644 tests/walker.test.ts diff --git a/DESIGN.md b/DESIGN.md index d707e35..1d7fa1f 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -11,10 +11,10 @@ Once every file has been parsed, generate a JSON with the information gathered a ## High-level implementation ```ts -public walker(root: string, filesExploredPath: Array): Array +public reader(root: string): Array ``` -The function `walker` will start at the root directory and save those files candidates of having components defined inside of them. Only will consider files with `.ts` extension. Should traverse all the file tree from the root up to the bottom and return the full path to every candidate file. +The function `reader` will start at the root directory and save those files candidates of having components defined inside of them. Only will consider files with `.ts` extension. Should traverse all the file tree from the root up to the bottom and return the full path to every candidate file, the content of the file and the type of definition that contains that file (component, pipe or directive). ```ts public parser(filePaths: Array): Array diff --git a/src/index.ts b/src/index.ts index f585b92..25a29d9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,12 @@ const path = require("path"); const argv = process.argv; -import * as walker from "./walker"; +import * as reader from "./reader"; import * as parser from "./parser"; import * as generator from "./generator"; import { File } from "./shared/IFile"; import logger from "./shared/logger"; import { ICLIConfig } from "./shared/ICLIConfig"; +import { FileType } from "./shared/constants"; let config: ICLIConfig = { workingDir: null, @@ -65,7 +66,7 @@ export const run = async (args: string[]) => { process.env.ROOT_PROJECT_PATH = config.workingDir || path.posix.resolve(); - let candidateFilePaths: Array = walker.walker(process.env.ROOT_PROJECT_PATH as string, []); + let candidateFilePaths: Array<{ type: FileType; filePath: string; fileData: string }> = reader.reader(process.env.ROOT_PROJECT_PATH as string); let fileData: Array = parser.parser(candidateFilePaths); generator.generator(fileData, config.outputDir as string); }; diff --git a/src/reader.ts b/src/reader.ts new file mode 100644 index 0000000..7eb805b --- /dev/null +++ b/src/reader.ts @@ -0,0 +1,49 @@ +import { Dirent } from "fs"; +import { FileType } from "./shared/constants"; +import logger from "./shared/logger"; + +const fs = require("fs"); +const path = require("path"); + +export const reader = (root: string): Array<{ type: FileType; filePath: string; fileData: string }> => { + let pendingDir: Array = [root], + result: Array<{ type: FileType; filePath: string; fileData: string }> = []; + + while (pendingDir.length > 0) { + let currentDir = pendingDir.shift(); + + if (currentDir == null) break; + + const files: Dirent[] = fs.readdirSync(currentDir, { + encoding: "utf8", + withFileTypes: true, + }); + + for (let file of files) { + if (file.isFile() && file.name.endsWith(".ts")) { + logger.log("Candidate file to contain component definition:", file.name); + const filePath = path.join(currentDir, file.name); + const fileData: string = fs.readFileSync(filePath, { + encoding: "utf8", + flag: "r", + }); + + let type: FileType = "CLASS"; + if (fileData?.match(/@Component/g)?.length || 0 > 0) { + type = "COMPONENT"; + } + + // In the future we will add FileType PIPE & FileType DIRECTIVE + + result.push({ + type, + filePath, + fileData, + }); + } else if (file.isDirectory() && file.name != "node_modules" && file.name != ".git") { + pendingDir.push(path.join(currentDir, file.name)); + } + } + } + return result; +}; diff --git a/src/walker.ts b/src/walker.ts deleted file mode 100644 index 2a68440..0000000 --- a/src/walker.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Dirent } from "fs"; -import logger from "./shared/logger"; - -const fs = require("fs"); -const path = require("path"); - -// TODO: Parse files synchronously -export const walker = (root: string, filesExploredPath: Array): Array => { - let pendingDir: Array = [root]; - - while (pendingDir.length > 0) { - let currentDir = pendingDir.shift(); - - if (currentDir == null) break; - - const files: Dirent[] = fs.readdirSync(currentDir, { - encoding: "utf8", - withFileTypes: true, - }); - - for (let file of files) { - if (file.isFile() && file.name.endsWith(".ts")) { - logger.log("Candidate file to contain component definition:", file.name); - filesExploredPath.push(path.join(currentDir, file.name)); - } else if (file.isDirectory() && file.name != "node_modules" && file.name != ".git") { - pendingDir.push(path.join(currentDir, file.name)); - } - } - } - return filesExploredPath; -}; diff --git a/tests/fixtures/ts-files/foo.ts b/tests/fixtures/ts-files/foo.ts index e69de29..e5df803 100644 --- a/tests/fixtures/ts-files/foo.ts +++ b/tests/fixtures/ts-files/foo.ts @@ -0,0 +1,5 @@ +import { Input } from "@angular/core"; + +export class BaseComponent { + @Input() baseInput: "type1" | "type2" | "type3"; +} diff --git a/tests/fixtures/ts-files/index.ts b/tests/fixtures/ts-files/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/tests/reader.test.ts b/tests/reader.test.ts new file mode 100644 index 0000000..649293e --- /dev/null +++ b/tests/reader.test.ts @@ -0,0 +1,17 @@ +import * as reader from "../src/reader"; +const path = require("path"); + +test("Inspects /tests/fixtures/ts-files with reader function", () => { + expect(reader.reader(path.join(path.posix.resolve(), "tests/fixtures/ts-files"))).toStrictEqual([ + { + type: "COMPONENT", + filePath: path.join(path.posix.resolve(), "/tests/fixtures/ts-files/foo.ts"), + fileData: + 'import { Input } from "@angular/core";\r\n' + + "\r\n" + + "export class BaseComponent {\r\n" + + ' @Input() baseInput: "type1" | "type2" | "type3";\r\n' + + "}\r\n", + }, + ]); +}); diff --git a/tests/walker.test.ts b/tests/walker.test.ts deleted file mode 100644 index 8767225..0000000 --- a/tests/walker.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { markAsUntransferable } from "worker_threads"; -import * as walker from "../src/walker"; -const path = require("path"); - -test("Inspects /tests/fixtures/ts-files with walker function", () => { - expect(walker.walker(path.join(path.posix.resolve(), "tests/fixtures/ts-files"), [])).toStrictEqual([ - path.join(path.posix.resolve(), "/tests/fixtures/ts-files/foo.ts"), - path.join(path.posix.resolve(), "/tests/fixtures/ts-files/index.ts"), - ]); -}); From 49c0becc7335da803c097c8a70fabacdacec7d12 Mon Sep 17 00:00:00 2001 From: roguib Date: Wed, 22 Sep 2021 09:45:49 +0200 Subject: [PATCH 07/12] increase test coverage of reader function --- tests/fixtures/ts-files/var.ts | 8 ++++++++ tests/reader.test.ts | 17 +++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/ts-files/var.ts diff --git a/tests/fixtures/ts-files/var.ts b/tests/fixtures/ts-files/var.ts new file mode 100644 index 0000000..1b76cee --- /dev/null +++ b/tests/fixtures/ts-files/var.ts @@ -0,0 +1,8 @@ +import { Input } from "@angular/core"; +@Component({ + selector: "app-main", + templateUrl: "./app.main.component.html", +}) +export class BaseComponent { + @Input() baseInput: "type1" | "type2" | "type3"; +} diff --git a/tests/reader.test.ts b/tests/reader.test.ts index 649293e..fb71747 100644 --- a/tests/reader.test.ts +++ b/tests/reader.test.ts @@ -4,8 +4,8 @@ const path = require("path"); test("Inspects /tests/fixtures/ts-files with reader function", () => { expect(reader.reader(path.join(path.posix.resolve(), "tests/fixtures/ts-files"))).toStrictEqual([ { - type: "COMPONENT", - filePath: path.join(path.posix.resolve(), "/tests/fixtures/ts-files/foo.ts"), + type: "CLASS", + filePath: "C:\\Users\\roger\\oos\\angular-vs-snippets\\tests\\fixtures\\ts-files\\foo.ts", fileData: 'import { Input } from "@angular/core";\r\n' + "\r\n" + @@ -13,5 +13,18 @@ test("Inspects /tests/fixtures/ts-files with reader function", () => { ' @Input() baseInput: "type1" | "type2" | "type3";\r\n' + "}\r\n", }, + { + type: "COMPONENT", + filePath: "C:\\Users\\roger\\oos\\angular-vs-snippets\\tests\\fixtures\\ts-files\\var.ts", + fileData: + 'import { Input } from "@angular/core";\r\n' + + "@Component({\r\n" + + ' selector: "app-main",\r\n' + + ' templateUrl: "./app.main.component.html",\r\n' + + "})\r\n" + + "export class BaseComponent {\r\n" + + ' @Input() baseInput: "type1" | "type2" | "type3";\r\n' + + "}\r\n", + }, ]); }); From 938f4982efa8acbf3d640d9d1016c55aed0ba53c Mon Sep 17 00:00:00 2001 From: roguib Date: Fri, 24 Sep 2021 21:44:23 +0200 Subject: [PATCH 08/12] move the reading process from the parser and move it to the reader --- src/parser.ts | 49 ++++++++------------ src/utils/path-resolver.ts | 3 +- tests/parser.test.ts | 93 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 109 insertions(+), 36 deletions(-) diff --git a/src/parser.ts b/src/parser.ts index e392fe6..a1bbca9 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1,37 +1,26 @@ const fs = require("fs"); const path = require("path"); +import { FileType } from "./shared/constants"; import { File, Input, Output } from "./shared/IFile"; import logger from "./shared/logger"; import pathResolver from "./utils/path-resolver"; import { REGEX_SELECTORS } from "./utils/regexSelectors"; -// TODO: class implementation with inputs/outputs defined - // TODO: Test in other OS (github actions) -// TODO: Read files asynchronously to improve performance? -export const parser = (filePaths: Array): Array => { - let result: Array = []; - // a temporal variable used for storing @Inputs/@Outputs declared on a parent class - let tmp: Array> = []; - - for (const filePath of filePaths) { - let file: string = fs.readFileSync(filePath, { - encoding: "utf8", - flag: "r", - }); +export const parser = (data: Array<{ type: FileType; filePath: string; fileData: string }>): Array => { + let result: Array = [], + tmp: Array> = []; // a temporal variable used for storing @Inputs/@Outputs declared on a parent class - let containsComponentDef = false; - if (file?.match(/@Component/g)?.length || 0 > 0) { - containsComponentDef = true; - } + for (const item of data) { + let containsComponentDef = item.type === "COMPONENT" ? true : false; - if (!containsComponentDef && file?.match(/@Input/g) == null && file?.match(/@Output/g) == null) { - logger.log("No component, Inputs or Outputs defined in this file"); + if (!containsComponentDef && item.fileData?.match(/@Input/g) == null && item.fileData?.match(/@Output/g) == null) { + logger.log("No component, Inputs or Outputs defined in this file:", item.filePath); continue; } - let fileNameData: Array = file?.match(REGEX_SELECTORS.componentSelector) || []; + let fileNameData: Array = item.fileData?.match(REGEX_SELECTORS.componentSelector) || []; if (fileNameData.length === 0) { logger.warn("Component tag not defined by any class."); continue; @@ -45,9 +34,9 @@ export const parser = (filePaths: Array): Array => { let selector = ""; if (containsComponentDef) { // match returns a string not an array - let componentSelectorData: Array = file?.match(REGEX_SELECTORS.componentHTMLselector) || []; + let componentSelectorData: Array = item.fileData?.match(REGEX_SELECTORS.componentHTMLselector) || []; if (componentSelectorData.length === 0) { - logger.warn("Component doesn't define any selector but contains @Component anotation."); + logger.warn("Component doesn't define any selector but contains @Component anotation:", item.filePath); continue; } componentSelectorData[0].replace(/(\s+)/g, " "); @@ -55,26 +44,26 @@ export const parser = (filePaths: Array): Array => { logger.log("Selector:", selector); } - let inputs: Array = parseInputs(file), - outputs: Array = parseOutputs(file); + let inputs: Array = parseInputs(item.fileData), + outputs: Array = parseOutputs(item.fileData); let extendedClassPath; - if (file?.match(REGEX_SELECTORS.extendedClassSelector)) { + if (item.fileData?.match(REGEX_SELECTORS.extendedClassSelector)) { // we should see if the extended class is in tmp and if not extract the inputs defined inside - let matchExtendedClass: Array = file?.match(REGEX_SELECTORS.extendedClassSelector) || []; + let matchExtendedClass: Array = item.fileData?.match(REGEX_SELECTORS.extendedClassSelector) || []; // resolve the path of the class let extendedClass: string = matchExtendedClass[0].replace(/(\s+)/g, " ").split(" ")[4]; logger.log("extendedClassName:", extendedClass); - let matchExtendedClassPath: Array = file?.match(REGEX_SELECTORS.extendedClassPathSelector) || []; + let matchExtendedClassPath: Array = item.fileData?.match(REGEX_SELECTORS.extendedClassPathSelector) || []; - extendedClassPath = pathResolver.resolve(filePath, matchExtendedClassPath[0]); + extendedClassPath = pathResolver.resolve(item.filePath, matchExtendedClassPath[0]); logger.log("path:", extendedClassPath); } if (containsComponentDef) { result.push({ - fileLocation: filePath, + fileLocation: item.filePath, // TODO: Rename it to filePath prefix: selector, componentName: componentName, inputs: inputs, @@ -90,7 +79,7 @@ export const parser = (filePaths: Array): Array => { * the amount of times we call path.join(path.posix.resolve(), path); */ tmp.push({ - fileLocation: path.resolve(filePath), + fileLocation: path.resolve(item.filePath), inputs: inputs, outputs: outputs, extendedClassFilepath: undefined, diff --git a/src/utils/path-resolver.ts b/src/utils/path-resolver.ts index 86afd75..7ff9e1b 100644 --- a/src/utils/path-resolver.ts +++ b/src/utils/path-resolver.ts @@ -34,8 +34,9 @@ export const resolve = (filePath: string, importExpr: string): string | null => const rootProjectDir = process.env.ROOT_PROJECT_PATH; if (tsconfigFile == null) { + const tsconfigFilePath = path.resolve(`${rootProjectDir}/tsconfig.json`); tsconfigFile = JSON.parse( - fs.readFileSync(path.resolve(`${rootProjectDir}/tsconfig.json`), { + fs.readFileSync(tsconfigFilePath, { encoding: "utf8", flag: "r", }) diff --git a/tests/parser.test.ts b/tests/parser.test.ts index 18cbbe7..a9d4fb8 100644 --- a/tests/parser.test.ts +++ b/tests/parser.test.ts @@ -1,7 +1,14 @@ import * as parser from "../src/parser"; const path = require("path"); +// TODO: Rewrite the tests so we have: +// 1. For normal inputs +// 2. For complex inputs +// 3. For normal outputs +// 4. For class inheritance + test("Parses the contents of the candidate files and returns an array of File type", async () => { + process.env.ROOT_PROJECT_PATH = "C:/Users/roger/oos/angular-vs-snippets/tests/fixtures/parser"; const result = [ { componentName: "MainComponent", @@ -96,9 +103,64 @@ test("Parses the contents of the candidate files and returns an array of File ty ]; expect( parser.parser([ - path.join(path.posix.resolve(), "/tests/fixtures/parser/main.component.ts"), - path.join(path.posix.resolve(), "/tests/fixtures/parser/base.component.ts"), - path.join(path.posix.resolve(), "/tests/fixtures/parser/component.module.ts"), + { + fileData: `import { Input } from \"@angular/core\", + export class BaseComponent { + @Input() baseInput: 'type1' | 'type2' | 'type3'; + }`, + filePath: "C:\\Users\\roger\\oos\\angular-vs-snippets\\tests\\fixtures\\parser\\base.component.ts", + type: "CLASS", + }, + { + fileData: `import { CommonModule } from '@angular/common'; + import { NgModule } from '@angular/core'; + + @NgModule({ + declarations: [Component], + imports: [CommonModule], + exports: [Component], + }) + export class ComponentModule {}`, + filePath: "C:\\Users\\roger\\oos\\angular-vs-snippets\\tests\\fixtures\\parser\\component.module.ts", + type: "CLASS", + }, + { + fileData: `import { Component, Input } from \"@angular/core\"; + import { BaseComponent } from \"./base.component\"; + + @Component({ + selector: \"app-main\", + templateUrl: \"./app.main.component.html\", + }) + export class MainComponent extends BaseComponent { + foo = false; + @Input() literalType1: \"type1\" | \"type2\" | \"type3\"; + @Input() literalType2: \"type1\" | \"type2\" | \"type3\" = \"type1\"; + @Input() literal_Type3: \"type1\" | \"type2\" | \"type3\" = \"type1\"; + @Input() appName: MediaModel; + @Input() foo: TypeError; + @Input() numberInput: number = 9; + @Input(\"inputNameA\") varName: type; + @Input(\"inputNameB\") varName: type; + @Input(\"inputNameC\") varName = \"adv\"; + @Input(\"inputNameD\") varName = 2354; + @Input() withoutType; + @Input() withoutTypeNorSemicolon; + @Input() variableAssignedValue = 9; + @Input() variableAssignedValueAndSemicolon = value; + @Output() buttonClick: EventEmitter = new EventEmitter(); + @Output() fooVar: EventEmitter = new EventEmitter(); + @Input() set Foo(value) {} + @Input() set FooType(value: string) {} + @Input() set FooTypeLiteral(value: \"literal1\" | \"literal2\" | \"literal3\") {} + + someRandomFunction(action) { + action = \"action\"; + } + }`, + filePath: "C:\\Users\\roger\\oos\\angular-vs-snippets\\tests\\fixtures\\parser\\main.component.ts", + type: "COMPONENT", + }, ]) ).toStrictEqual(result); }); @@ -126,8 +188,29 @@ test("Tests the parser when de file is imported using the @ special keyword path ]; expect( parser.parser([ - path.join(path.posix.resolve(), "/tests/fixtures/parser/special-path-tsconfig/special-base.component.ts"), - path.join(path.posix.resolve(), "/tests/fixtures/parser/special-path-tsconfig/special-path.component.ts"), + { + fileData: `import { Input } from \"@angular/core\"; + + export class SpecialBaseComponent { + @Input() baseInputInSpecialBaseClass: \"type1\" | \"type2\" | \"type3\"; + }`, + filePath: "C:\\Users\\roger\\oos\\angular-vs-snippets\\tests\\fixtures\\parser\\special-path-tsconfig\\special-base.component.ts", + type: "CLASS", + }, + { + fileData: `import { Component, Input } from \"@angular/core\"; + import { SpecialBaseComponent } from \"@special-base/special-base.component\"; + + @Component({ + selector: \"app-main\", + templateUrl: \"./app.main.component.html\", + }) + export class SpecialPathComponent extends SpecialBaseComponent { + @Input() inputInChildClass: \"type1\" | \"type2\" | \"type3\"; + }`, + filePath: "C:\\Users\\roger\\oos\\angular-vs-snippets\\tests\\fixtures\\parser\\special-path-tsconfig\\special-path.component.ts", + type: "COMPONENT", + }, ]) ).toStrictEqual(result); }); From 5d41b9d4d4303d1579d06c13fabc69c0636d5aa9 Mon Sep 17 00:00:00 2001 From: roguib Date: Sat, 25 Sep 2021 16:19:15 +0200 Subject: [PATCH 09/12] IFIleData.ts --- src/index.ts | 3 ++- src/parser.ts | 3 ++- src/reader.ts | 12 ++++++++++-- src/shared/IFileData.ts | 7 +++++++ 4 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 src/shared/IFileData.ts diff --git a/src/index.ts b/src/index.ts index 25a29d9..5e2ee15 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ import { File } from "./shared/IFile"; import logger from "./shared/logger"; import { ICLIConfig } from "./shared/ICLIConfig"; import { FileType } from "./shared/constants"; +import { IFileData } from "./shared/IFileData"; let config: ICLIConfig = { workingDir: null, @@ -66,7 +67,7 @@ export const run = async (args: string[]) => { process.env.ROOT_PROJECT_PATH = config.workingDir || path.posix.resolve(); - let candidateFilePaths: Array<{ type: FileType; filePath: string; fileData: string }> = reader.reader(process.env.ROOT_PROJECT_PATH as string); + let candidateFilePaths: Array = reader.reader(process.env.ROOT_PROJECT_PATH as string); let fileData: Array = parser.parser(candidateFilePaths); generator.generator(fileData, config.outputDir as string); }; diff --git a/src/parser.ts b/src/parser.ts index a1bbca9..938a2de 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -3,12 +3,13 @@ const path = require("path"); import { FileType } from "./shared/constants"; import { File, Input, Output } from "./shared/IFile"; +import { IFileData } from "./shared/IFileData"; import logger from "./shared/logger"; import pathResolver from "./utils/path-resolver"; import { REGEX_SELECTORS } from "./utils/regexSelectors"; // TODO: Test in other OS (github actions) -export const parser = (data: Array<{ type: FileType; filePath: string; fileData: string }>): Array => { +export const parser = (data: Array): Array => { let result: Array = [], tmp: Array> = []; // a temporal variable used for storing @Inputs/@Outputs declared on a parent class diff --git a/src/reader.ts b/src/reader.ts index 7eb805b..60e54a3 100644 --- a/src/reader.ts +++ b/src/reader.ts @@ -1,13 +1,21 @@ import { Dirent } from "fs"; import { FileType } from "./shared/constants"; +import { IFileData } from "./shared/IFileData"; import logger from "./shared/logger"; const fs = require("fs"); const path = require("path"); -export const reader = (root: string): Array<{ type: FileType; filePath: string; fileData: string }> => { +/** + * + * @param {string} root The root of the directory in which the function has + * to start reading files + * @returns {Array} An array that contains all the files that are candidates of having + * components or class definitions + */ +export const reader = (root: string): Array => { let pendingDir: Array = [root], - result: Array<{ type: FileType; filePath: string; fileData: string }> = []; + result: Array = []; while (pendingDir.length > 0) { let currentDir = pendingDir.shift(); diff --git a/src/shared/IFileData.ts b/src/shared/IFileData.ts new file mode 100644 index 0000000..6df8157 --- /dev/null +++ b/src/shared/IFileData.ts @@ -0,0 +1,7 @@ +import { FileType } from "./constants"; + +export interface IFileData { + type: FileType; + filePath: string; + fileData: string; +} From 5139a7aabd5a093563d6e2b9e4a37eea395c26f1 Mon Sep 17 00:00:00 2001 From: roguib Date: Sat, 25 Sep 2021 16:25:12 +0200 Subject: [PATCH 10/12] use filePath variable name across the project --- src/parser.ts | 6 +++--- src/shared/IFile.ts | 2 +- tests/parser.test.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/parser.ts b/src/parser.ts index 938a2de..6c2a5e5 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -64,7 +64,7 @@ export const parser = (data: Array): Array => { if (containsComponentDef) { result.push({ - fileLocation: item.filePath, // TODO: Rename it to filePath + filePath: item.filePath, prefix: selector, componentName: componentName, inputs: inputs, @@ -80,7 +80,7 @@ export const parser = (data: Array): Array => { * the amount of times we call path.join(path.posix.resolve(), path); */ tmp.push({ - fileLocation: path.resolve(item.filePath), + filePath: path.resolve(item.filePath), inputs: inputs, outputs: outputs, extendedClassFilepath: undefined, @@ -94,7 +94,7 @@ export const parser = (data: Array): Array => { for (let i = 0; i < result.length; ++i) { if (result[i].extendedClassFilepath) { for (let j = 0; j < tmp.length; ++j) { - if (result[i].extendedClassFilepath === tmp[j].fileLocation) { + if (result[i].extendedClassFilepath === tmp[j].filePath) { result[i].inputs = [...result[i].inputs, ...(tmp[j].inputs as [])] as Input[]; result[i].outputs = [...result[i].outputs, ...(tmp[j].outputs as [])] as Output[]; break; diff --git a/src/shared/IFile.ts b/src/shared/IFile.ts index c40ef7b..423417f 100644 --- a/src/shared/IFile.ts +++ b/src/shared/IFile.ts @@ -1,5 +1,5 @@ export interface File { - fileLocation: string; + filePath: string; prefix: string; componentName: string; inputs: Array; diff --git a/tests/parser.test.ts b/tests/parser.test.ts index a9d4fb8..6292771 100644 --- a/tests/parser.test.ts +++ b/tests/parser.test.ts @@ -13,7 +13,7 @@ test("Parses the contents of the candidate files and returns an array of File ty { componentName: "MainComponent", extendedClassFilepath: path.join(path.posix.resolve(), "/tests/fixtures/parser/base.component.ts"), - fileLocation: path.join(path.posix.resolve(), "/tests/fixtures/parser/main.component.ts"), + filePath: path.join(path.posix.resolve(), "/tests/fixtures/parser/main.component.ts"), inputs: [ { inputName: "literalType1", @@ -171,7 +171,7 @@ test("Tests the parser when de file is imported using the @ special keyword path { componentName: "SpecialPathComponent", extendedClassFilepath: path.join(path.posix.resolve(), "/tests/fixtures/parser/special-path-tsconfig/special-base.component.ts"), - fileLocation: path.join(path.posix.resolve(), "/tests/fixtures/parser/special-path-tsconfig/special-path.component.ts"), + filePath: path.join(path.posix.resolve(), "/tests/fixtures/parser/special-path-tsconfig/special-path.component.ts"), inputs: [ { inputName: "inputInChildClass", From 22b4ca809fcd1aad4d79b9f1b5c476657750fb18 Mon Sep 17 00:00:00 2001 From: roguib Date: Sat, 25 Sep 2021 16:32:18 +0200 Subject: [PATCH 11/12] doc --- src/generator.ts | 6 ++++++ src/parser.ts | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/src/generator.ts b/src/generator.ts index c58d49e..f2488d2 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -3,6 +3,12 @@ import logger from "./shared/logger"; const fs = require("fs"); const path = require("path"); +/** + * + * @param {Array} files An array of tokens generated after parsing the content of every file + * @param {string} outputPath The location in which the function has to write the file with the deffinitions + * of the snippets + */ export const generator = (files: Array, outputPath: string): void => { // scope will be html only for now let json = Object(); diff --git a/src/parser.ts b/src/parser.ts index 6c2a5e5..6c7ee1d 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -9,6 +9,11 @@ import pathResolver from "./utils/path-resolver"; import { REGEX_SELECTORS } from "./utils/regexSelectors"; // TODO: Test in other OS (github actions) +/** + * + * @param {IFIleData} data An array of files that can contain component or class definitions + * @returns {File} An array filed of tokens that contains information about each component parsed + */ export const parser = (data: Array): Array => { let result: Array = [], tmp: Array> = []; // a temporal variable used for storing @Inputs/@Outputs declared on a parent class From 82242766000912781854177466472d6b4809e0c3 Mon Sep 17 00:00:00 2001 From: roguib Date: Tue, 28 Sep 2021 09:12:40 +0200 Subject: [PATCH 12/12] Closes #9 --- src/parser.ts | 205 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 134 insertions(+), 71 deletions(-) diff --git a/src/parser.ts b/src/parser.ts index 6c7ee1d..93147be 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -19,77 +19,20 @@ export const parser = (data: Array): Array => { tmp: Array> = []; // a temporal variable used for storing @Inputs/@Outputs declared on a parent class for (const item of data) { - let containsComponentDef = item.type === "COMPONENT" ? true : false; - - if (!containsComponentDef && item.fileData?.match(/@Input/g) == null && item.fileData?.match(/@Output/g) == null) { - logger.log("No component, Inputs or Outputs defined in this file:", item.filePath); - continue; - } - - let fileNameData: Array = item.fileData?.match(REGEX_SELECTORS.componentSelector) || []; - if (fileNameData.length === 0) { - logger.warn("Component tag not defined by any class."); - continue; - } - - // we consider only one component per file - fileNameData[0].replace(/(\s+)/, " "); - const componentName: string = fileNameData[0].split(" ")[2]; - logger.log("Name of the component:", componentName); - - let selector = ""; - if (containsComponentDef) { - // match returns a string not an array - let componentSelectorData: Array = item.fileData?.match(REGEX_SELECTORS.componentHTMLselector) || []; - if (componentSelectorData.length === 0) { - logger.warn("Component doesn't define any selector but contains @Component anotation:", item.filePath); - continue; - } - componentSelectorData[0].replace(/(\s+)/g, " "); - selector = componentSelectorData[0].split(" ")[1].replace(/('|")/g, ""); - logger.log("Selector:", selector); - } - - let inputs: Array = parseInputs(item.fileData), - outputs: Array = parseOutputs(item.fileData); - - let extendedClassPath; - if (item.fileData?.match(REGEX_SELECTORS.extendedClassSelector)) { - // we should see if the extended class is in tmp and if not extract the inputs defined inside - let matchExtendedClass: Array = item.fileData?.match(REGEX_SELECTORS.extendedClassSelector) || []; - // resolve the path of the class - let extendedClass: string = matchExtendedClass[0].replace(/(\s+)/g, " ").split(" ")[4]; - logger.log("extendedClassName:", extendedClass); - let matchExtendedClassPath: Array = item.fileData?.match(REGEX_SELECTORS.extendedClassPathSelector) || []; - - extendedClassPath = pathResolver.resolve(item.filePath, matchExtendedClassPath[0]); - - logger.log("path:", extendedClassPath); - } - - if (containsComponentDef) { - result.push({ - filePath: item.filePath, - prefix: selector, - componentName: componentName, - inputs: inputs, - outputs: outputs, - extendedClassFilepath: extendedClassPath || undefined, - }); - } else { - /** - * TODO: Instead of working with relative paths and converting them - * to absolute when needed, we can start the exec by transforming every - * relative path to absolute, and then clean up the resolve calls in the program - * that transforms the code into an spaguetti one. Also it could help by reducing - * the amount of times we call path.join(path.posix.resolve(), path); - */ - tmp.push({ - filePath: path.resolve(item.filePath), - inputs: inputs, - outputs: outputs, - extendedClassFilepath: undefined, - }); + let parsedData; + switch (item.type) { + case "CLASS": + parsedData = parseClassDefinition(item); + if (parsedData) { + tmp.push(parsedData); + } + break; + default: + parsedData = parseComponentDefinition(item); + if (parsedData) { + result.push(parsedData); + } + break; } } // we're asuming there won't be a lot of classes extending others @@ -110,6 +53,65 @@ export const parser = (data: Array): Array => { return result; }; +/** + * @private + * @param {IFIleData} file A file that is CLASS type + * @returns {string | undefined} A token with the data extracted from the class definition file + */ +const parseClassDefinition = (file: IFileData): Partial | undefined => { + if (file.fileData?.match(/@Input/g) == null && file.fileData?.match(/@Output/g) == null) { + logger.log("No Inputs or Outputs defined in this file class:", file.filePath); + return undefined; + } + + const inputs: Array = parseInputs(file.fileData), + outputs: Array = parseOutputs(file.fileData), + extendedClassFilepath = parseExtendedClassPath(file); + /** + * TODO: Instead of working with relative paths and converting them + * to absolute when needed, we can start the exec by transforming every + * relative path to absolute, and then clean up the resolve calls in the program + * that transforms the code into an spaguetti one. Also it could help by reducing + * the amount of times we call path.join(path.posix.resolve(), path); + */ + return { + filePath: file.filePath, + inputs, + outputs, + extendedClassFilepath, + }; +}; + +/** + * @private + * @param {IFIleData} file A file that is COMPONENT type + * @returns {string | undefined} A token with the data extracted from the class definition file + */ +const parseComponentDefinition = (file: IFileData): File | undefined => { + if (file.fileData?.match(/@Input/g) == null && file.fileData?.match(/@Output/g) == null) { + logger.log("No component, Inputs or Outputs defined in this file:", file.filePath); + return undefined; + } + + const componentName = parseComponentName(file.fileData), + prefix = parseComponentSelector(file); + if (!componentName || !prefix) { + return undefined; + } + const inputs: Array = parseInputs(file.fileData), + outputs: Array = parseOutputs(file.fileData), + extendedClassFilepath = parseExtendedClassPath(file); + + return { + filePath: file.filePath, + prefix, + componentName, + inputs, + outputs, + extendedClassFilepath, + }; +}; + /** * @private * @param {string} file a string where to look for input definitions @@ -256,3 +258,64 @@ const parseOutputs = (file: string): Array => { return outputs; }; + +/** + * @private + * @param {IFIleData} file The class definition that is candidate to extend another class + * @returns {string | undefined} The absolute resolved extended class path or undefined if doesn´t extend any class + */ +const parseExtendedClassPath = (file: IFileData): string | undefined => { + let extendedClassPath: string | undefined; + if (file.fileData?.match(REGEX_SELECTORS.extendedClassSelector)) { + // we should see if the extended class is in tmp and if not extract the inputs defined inside + let matchExtendedClass: Array = file.fileData?.match(REGEX_SELECTORS.extendedClassSelector) || []; + // resolve the path of the class + let extendedClass: string = matchExtendedClass[0].replace(/(\s+)/g, " ").split(" ")[4]; + logger.log("extendedClassName:", extendedClass); + let matchExtendedClassPath: Array = file.fileData?.match(REGEX_SELECTORS.extendedClassPathSelector) || []; + + extendedClassPath = pathResolver.resolve(file.filePath, matchExtendedClassPath[0]) as string; + + logger.log("path:", extendedClassPath); + } + + return extendedClassPath; +}; + +/** + * @private + * @param {string} file + * @returns {string | undefined} The name of the component + */ +const parseComponentName = (file: string): string | undefined => { + let fileNameData: Array = file?.match(REGEX_SELECTORS.componentSelector) || []; + if (fileNameData.length === 0) { + logger.warn("Component tag not defined by any class."); + return undefined; + } + + // we consider only one component per file + fileNameData[0].replace(/(\s+)/, " "); + const componentName: string = fileNameData[0].split(" ")[2]; + logger.log("Name of the component:", componentName); + return componentName; +}; + +/** + * @private + * @param {IFIleData} file + * @returns {string | undefined} The component selector + */ +const parseComponentSelector = (file: IFileData): string | undefined => { + let selector; + // match returns a string not an array + let componentSelectorData: Array = file.fileData?.match(REGEX_SELECTORS.componentHTMLselector) || []; + if (componentSelectorData.length === 0) { + logger.warn("Component doesn't define any selector but contains @Component anotation:", file.filePath); + return undefined; + } + componentSelectorData[0].replace(/(\s+)/g, " "); + selector = componentSelectorData[0].split(" ")[1].replace(/('|")/g, ""); + logger.log("Selector:", selector); + return selector; +};