Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,11 @@
"request": "launch",
"name": "End to end execution",
"preLaunchTask": "npm: build:cjs",
"program": "./build/ng-vs-snippets",
"args":
[
"--dir",
"./src",
"--output",
"./.vscode"
],
"program": "./build/@roguib/ng-vs-snippets.js",
"args": ["--dir", "./src", "--output", "./.vscode"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
"disableOptimisticBPs": true
}
]
}
31 changes: 16 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
# ng-vs-snippets
![ts](https://badgen.net/badge/Built%20With/TypeScript/blue) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![made-for-VSCode](https://img.shields.io/badge/Made%20for-VSCode-1f425f.svg)](https://code.visualstudio.com/) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)


![ts](https://badgen.net/badge/Built%20With/TypeScript/blue) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![made-for-VSCode](https://img.shields.io/badge/Made%20for-VSCode-1f425f.svg)](https://code.visualstudio.com/) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)

Automatic VS Code snippet file generation for Angular codebases. Seamlessly maintain up-to-date VS Code snippets of all of your code.

Currently, we support snippet generation from the following Angular elements:

<center>

| Element | Status |
|-----------|:-----------------------------------------:|
| Component | :white_check_mark: Supported |
| Directive | :construction_worker: Not yet supported |
| Pipe | :construction_worker: Not yet supported |
| Element | Status |
| --------- | :-------------------------------------: |
| Component | :white_check_mark: Supported |
| Directive | :construction_worker: Not yet supported |
| Pipe | :construction_worker: Not yet supported |

</center>

## Installation

Install ```ng-vs-snippets``` as a dev-dependency in your Angular project. To do so, run:
Install `ng-vs-snippets` as a dev-dependency in your Angular project. To do so, run:

```
npm i ng-vs-snippets --save-dev
npm i @roguib/ng-vs-snippets --save-dev
```

Create a ```package.json``` script to extract snippets from your codebase:
Create a `package.json` script to extract snippets from your codebase:

```json
"scripts": {
"ng-vs-snippets": "ng-vs-snippets --dir ./src --output ./.vscode",
"ng-vs-snippets": "ng-vs-snippets --dir . --output ./.vscode",
},
```

Expand All @@ -39,21 +38,23 @@ Execute the script by running:
npm run ng-vs-snippets
```

The script will generate a ```out.code-snippets``` file containing all the definitions. **Make sure you don't have any file with the same name** since the data contained in that file **is going to be replaced**.
The script will generate a `out.code-snippets` file containing all the definitions. **Make sure you don't have any file with the same name** since the data contained in that file **is going to be replaced**.

## Troubleshooting
Sometimes, due to VS Code configuration issues, snippets don't appear in the suggestion's dropdown. Make sure to specify, in VS Code ```settings.json``` configuration file the following properties:

Sometimes, due to VS Code configuration issues, snippets don't appear in the suggestion's dropdown. Make sure to specify, in VS Code `settings.json` configuration file the following properties:

```json
"editor.tabCompletion": "on",
"editor.snippetSuggestions": "top"
```
If this doesn't fix the problem, open the command palette and search for ```Preferences: Configure User Snippets``` to ensure the editor is considering the fille where your generated snippets are defined.

If this doesn't fix the problem, open the command palette and search for `Preferences: Configure User Snippets` to ensure the editor is considering the fille where your generated snippets are defined.

## Documentation

You can find the [full document design at this url]().

## Contributing

Pull requests are welcome :) Make sure to create an issue first so anyone's work is overlaped.
Pull requests are welcome :) Make sure to create an issue first so anyone's work is overlaped.
2 changes: 1 addition & 1 deletion package-lock.json

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

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@
"angular",
"vs snippets"
],
"main": "build/ng-vs-snippets.js",
"types": "build/ng-vs-snippets.d.ts",
"main": "build/@roguib/ng-vs-snippets.js",
"types": "build/@roguib/ng-vs-snippets.d.ts",
"bin": {
"ng-vs-snippets": "build/ng-vs-snippets.js"
"ng-vs-snippets": "build/@roguib/ng-vs-snippets.js"
},
"scripts": {
"build:cjs": "rollup -c",
"check": "tsc",
"run": "node ./build/index.js",
"run": "node ./build/@roguib/ng-vs-snippets.js",
"test": "npm run build:cjs && jest ./tests/*"
},
"dependencies": {
Expand Down
18 changes: 9 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
const fs = require("fs");
const path = require("path");
const readline = require("readline");
const argv = process.argv;
import * as walker from "./walker";
import * as parser from "./parser";
Expand All @@ -17,6 +15,9 @@ let config: ICLIConfig = {

const parseArgs = (args: string[]) => {
// TODO: Include multiple options

logger.log("args:", args);

while (args.length > 0) {
const arg = args.shift();

Expand Down Expand Up @@ -52,25 +53,24 @@ export const verifyArgs = () => {
if (config.outputDir == null) {
logger.err("No output directory specified. Aborting.");
}
}
};

export const run = async (args: string[]) => {
parseArgs(args);
verifyArgs();
// const config = parseArgs(args);

if (config.debug) {
logger.enableDebugger();
}

process.env.ROOT_PROJECT_PATH = config.workingDir || path.posix.resolve();

let candidateFilePaths: Array<string> = walker.walker(
config.workingDir || path.posix.resolve(),
process.env.ROOT_PROJECT_PATH as string,
[]
);
let fileData: Array<File> = parser.parser(candidateFilePaths);
generator.generator(
fileData,
config.outputDir as string
);
generator.generator(fileData, config.outputDir as string);
};

run(argv.slice(2, argv.length));
100 changes: 36 additions & 64 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const path = require("path");

import { File, Input, Output } from "./shared/IFile";
import logger from "./shared/logger";
import pathResolver from "./utils/path-resolver";

// selectors
const componentSelector = /export(\s+)class(\s+)[a-zA-Z0-9-_]+/g;
Expand Down Expand Up @@ -31,11 +32,11 @@ const regularOutputSelector = /@Output\(\)(\s+)[a-zA-Z0-9-_]+:(\s+)EventEmitter<

// 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;
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 in other OS (github actions)
// TODO: Read files synchronously
// TODO: Test in other OS (github actions)
// TODO: Read files asynchronously to improve performance?
export const parser = (filePaths: Array<string>): Array<File> => {
let result: Array<File> = [];
// a temporal variable used for storing @Inputs/@Outputs declared on a parent class
Expand All @@ -61,8 +62,7 @@ export const parser = (filePaths: Array<string>): Array<File> => {
continue;
}

let fileNameData: Array<string> =
file?.match(componentSelector) || [];
let fileNameData: Array<string> = file?.match(componentSelector) || [];
if (fileNameData.length === 0) {
logger.warn("Component tag not defined by any class.");
continue;
Expand Down Expand Up @@ -93,15 +93,14 @@ export const parser = (filePaths: Array<string>): Array<File> => {
// Input() foo: 'type1' | 'type2'
let inputs: Array<Input> = [];
let inputsData: Array<string> =
file?.match(
regularInputLiteralTypeSelector
) || [];
file?.match(regularInputLiteralTypeSelector) || [];
for (let input of inputsData) {
logger.log('inputs parsed:', inputsData);
logger.log("inputs parsed:", inputsData);
let tmp: Array<string> = input.replace(/(\s)+/g, " ").split(" ");
let type = tmp
.slice(2, tmp.length)
.join()
.replace(/\"/g, "'")
.replace(";", "")
.replace(/,/g, "");
inputs.push({
Expand All @@ -113,13 +112,9 @@ export const parser = (filePaths: Array<string>): Array<File> => {

// @Input() variableName: type; and @Input() variableName: number = 9;
inputsData = [];
inputsData =
file?.match(
regularInputWithTypeSelector
) || [];
inputsData = file?.match(regularInputWithTypeSelector) || [];
for (let input of inputsData) {
let tmp: Array<string> = input.replace(/(\s+)/g, " ").split(" ");
logger.log("input data", inputsData);
inputs.push({
inputName: tmp[1].replace(":", ""),
type: tmp[2].replace(";", ""),
Expand All @@ -129,16 +124,11 @@ export const parser = (filePaths: Array<string>): Array<File> => {

inputsData = [];
// @Input('inputName') varName: type; and @Input("inputName") varName: type
inputsData =
file?.match(
customNameInputWithTypeSelector
) || [];
inputsData = file?.match(customNameInputWithTypeSelector) || [];
for (let input of inputsData) {
let tmp: Array<string> = 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(";", ""),
Expand All @@ -149,10 +139,7 @@ export const parser = (filePaths: Array<string>): Array<File> => {
// @Input('inputNameC') varName = 'adv';
// @Input("inputNameD") varName = 2354;
inputsData = [];
inputsData =
file?.match(
setterInputCustomNameSelector
) || [];
inputsData = file?.match(setterInputCustomNameSelector) || [];
for (let input of inputsData) {
let tmp: Array<string> = input.replace(/(\s+)/g, " ").split(" ");
const inputName = (tmp[0].match(/('|")[a-zA-Z0-9-_]+('|")/g) || [
Expand All @@ -167,10 +154,7 @@ export const parser = (filePaths: Array<string>): Array<File> => {

//@Input() set foo(value) {}
inputsData = [];
inputsData =
file?.match(
setterInputSelector
) || [];
inputsData = file?.match(setterInputSelector) || [];
for (let input of inputsData) {
let tmp: Array<string> = input.replace(/(\s+)/g, " ").split(" ");
const inputName = tmp[2].replace(/(\s+)/g, "").split("(")[0];
Expand All @@ -183,10 +167,7 @@ export const parser = (filePaths: Array<string>): Array<File> => {

//@Input() set foo(value: type) {}
inputsData = [];
inputsData =
file?.match(
setterInputWithTypeSelector
) || [];
inputsData = file?.match(setterInputWithTypeSelector) || [];
for (let input of inputsData) {
let tmp: Array<string> = input.replace(/(\s+)/g, " ").split(" ");
const inputName = tmp[2].replace(/(\s+)/g, "").split("(")[0];
Expand All @@ -200,18 +181,15 @@ export const parser = (filePaths: Array<string>): Array<File> => {

//@Input() set foo(value: 'type1' | 'type2') {}
inputsData = [];
inputsData =
file?.match(
setterInputLiteralTypeSelector
) || [];
inputsData = file?.match(setterInputLiteralTypeSelector) || [];
for (let input of inputsData) {
let tmp: Array<string> = 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, " ");
.slice(3, tmp.length)
.join()
.replace(/'|"|\)/g, "")
.replace(/,/g, " ");
inputs.push({
inputName,
type,
Expand Down Expand Up @@ -239,10 +217,7 @@ export const parser = (filePaths: Array<string>): Array<File> => {

let outputs: Array<Output> = [];
// only @Output() buttonClick: EventEmitter<any> = new EventEmitter(); for now
let outputsData: Array<string> =
file?.match(
regularOutputSelector
) || [];
let outputsData: Array<string> = file?.match(regularOutputSelector) || [];
for (let output of outputsData) {
let tmp: Array<string> = output.replace(/(\s+)/g, " ").split(" ");
outputs.push({
Expand All @@ -257,33 +232,23 @@ export const parser = (filePaths: Array<string>): Array<File> => {
logger.log("Outputs detected:", outputs);

let extendedClassPath;
if (
file?.match(
extendedClassSelector
)
) {
if (file?.match(extendedClassSelector)) {
// we should see if the extended class is in tmp and if not extract the inputs defined inside
let matchExtendedClass: Array<string> =
file?.match(
extendedClassSelector
) || [];
file?.match(extendedClassSelector) || [];
// resolve the path of the class
let extendedClass: string = matchExtendedClass[0]
.replace(/(\s+)/g, " ")
.split(" ")[4];
logger.log("extendedClassName:", extendedClass);
let matchExtendedClassPath: Array<string> =
file?.match(
extendedClassPathSelector
) || [];
// TODO: Document this in notes. Notice that by using path.join(path.dirname(fullComponentClassPath), relative path of the base path from ComponentClassPath) resolves into the full path of the base path
extendedClassPath = path.join(
path.dirname(filePath),
file?.match(extendedClassPathSelector) || [];

extendedClassPath = pathResolver.resolve(
filePath,
matchExtendedClassPath[0]
.replace(/(\s+)/g, " ")
.replace(/"/g, "")
.split(" ")[5] + ".ts"
);

logger.log("path:", extendedClassPath);
}

Expand All @@ -297,8 +262,15 @@ export const parser = (filePaths: Array<string>): Array<File> => {
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({
fileLocation: filePath,
fileLocation: path.resolve(filePath),
inputs: inputs,
outputs: outputs,
extendedClassFilepath: undefined,
Expand Down
Loading