Skip to content

Commit

Permalink
support JS dynamic imports
Browse files Browse the repository at this point in the history
  • Loading branch information
florianbgt committed Oct 30, 2024
1 parent 894ae57 commit 5feca2c
Show file tree
Hide file tree
Showing 3 changed files with 293 additions and 55 deletions.
64 changes: 33 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ $ napi ui
```

### Commands:

```
init Initialize the NanoAPI project
ui [entrypoint] Inspect the codebase and understand the API endpoints, middleware, and other API-specific code
Expand All @@ -87,43 +88,43 @@ You can also refactor your codebase by adding annotations to your code. Here is
```typescript
// src/api.js

import * as express from 'express';
import * as express from "express";

import * as service from './service.js';
import * as service from "./service.js";

const app = express();
const port = 3000;

app.use(express.json());

// @nanoapi endpoint:/api/v2/maths/time method:GET group:Time
app.get('/api/v2/maths/time', (req, res) => {
const result = service.time();
return res.json({
result
});
app.get("/api/v2/maths/time", (req, res) => {
const result = service.time();
return res.json({
result,
});
});

// @nanoapi endpoint:/api/v2/maths/addition method:POST group:Maths
app.post('/api/v2/maths/addition', (req, res) => {
const { body } = req;
const result = service.addition(body.a, body.b);
return res.json({
result
});
app.post("/api/v2/maths/addition", (req, res) => {
const { body } = req;
const result = service.addition(body.a, body.b);
return res.json({
result,
});
});

// @nanoapi endpoint:/api/v2/maths/subtraction method:POST group:Maths
app.post('/api/v2/maths/subtraction', (req, res) => {
const { body } = req;
const result = service.subtraction(body.a, body.b);
return res.json({
result
});
app.post("/api/v2/maths/subtraction", (req, res) => {
const { body } = req;
const result = service.subtraction(body.a, body.b);
return res.json({
result,
});
});

app.listen(port, () => {
console.log(`App listening on port: ${port}`);
console.log(`App listening on port: ${port}`);
});
```

Expand All @@ -134,24 +135,24 @@ From the exmaple above, you will get the following output from running a build:
```typescript
// dist/Time/src/api.js

import * as express from 'express';
import * as express from "express";

import * as service from './service.js';
import * as service from "./service.js";

const app = express();
const port = 3000;

app.use(express.json());

app.get('/api/v2/maths/time', (req, res) => {
const result = service.time();
return res.json({
result
});
app.get("/api/v2/maths/time", (req, res) => {
const result = service.time();
return res.json({
result,
});
});

app.listen(port, () => {
console.log(`App listening on port: ${port}`);
console.log(`App listening on port: ${port}`);
});
```

Expand All @@ -163,9 +164,10 @@ We welcome contributions from the community. Please read our [contributing guide

This project is in the early stages of development. We are actively working on the project and will be releasing new features and improvements regularly, which may include a rewrite into a more efficient and generic language like Rust or Go. Please check our issues and project board for more information, and don't for.

- [x] Support for NodeJS/Typescript and ExpressJS
- [x] Limited support for NodeJS/Typescript (no require or dynamic imports, no decorators)
- [x] Simple UI
- [ ] NestJS support
- [x] Support NodeJS require and dynamic imports
- [ ] Full support for NodeJS/Typescript
- [ ] Python support with Flask
- [ ] Django support
- [ ] Full python support
Expand All @@ -181,4 +183,4 @@ NanoAPI is a fair-source project. Because of this, we feel it would be unethical
- Money from the pool will be distributed to contributors
- At the end of the year, any remaining money will be donated to a charity of the community's choice

We will post regular updates on how much money is in the pool and how it is being distributed.
We will post regular updates on how much money is in the pool and how it is being distributed.
113 changes: 111 additions & 2 deletions packages/cli/src/helper/languages/javascript/cleanup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {
extractFileImportsFromRequireDeclarations,
extractIdentifiersFromImportStatement,
extractIdentifiersFromRequireDeclaration,
getDynamicImportDeclarations,
extractFileImportsFromDynamicImportDeclarations,
extractIdentifiersFromDynamicImportDeclaration,
} from "./imports";

function removeAnnotations(
Expand All @@ -32,11 +35,16 @@ function removeAnnotations(

if (!endpointToKeep) {
const nextNode = node.nextNamedSibling;
// TODO test this piece of code with decorators on a nestjs project
// // We need to remove all decorators too
// while (nextNode && nextNode.type === "decorator") {
// nextNode = nextNode.nextNamedSibling;
// }
if (!nextNode) {
throw new Error("Could not find next node");
}
// TODO support decorator. Lots of framework uses these (eg: nestjs)
// delete this node (comment) and the next node (api endpoint)

// delete this node (comment) and the next node(s) (api endpoint)
updatedSourceCode = updatedSourceCode.replace(
sourceCode.substring(node.startIndex, nextNode.endIndex + 1),
"",
Expand Down Expand Up @@ -116,6 +124,39 @@ function removeInvalidFileRequires(
return { updatedSourceCode, removedIdentifiers };
}

function removeInvalidFileDynamicImports(
rootNode: Parser.SyntaxNode,
sourceCode: string,
invalidDependencies: string[],
) {
let updatedSourceCode = sourceCode;
const removedIdentifiers: Parser.SyntaxNode[] = [];

const dynamicImportStatements = getDynamicImportDeclarations(rootNode);
dynamicImportStatements.forEach((dynamicImportStatement) => {
const importName = extractFileImportsFromDynamicImportDeclarations(
dynamicImportStatement,
);
if (importName && invalidDependencies.includes(importName)) {
const identifiers = extractIdentifiersFromDynamicImportDeclaration(
dynamicImportStatement,
);
removedIdentifiers.push(...identifiers);

// Remove the require statement
updatedSourceCode = updatedSourceCode.replace(
sourceCode.substring(
dynamicImportStatement.startIndex,
dynamicImportStatement.endIndex,
),
"",
);
}
});

return { updatedSourceCode, removedIdentifiers };
}

function removeDeletedImportUsage(
rootNode: Parser.SyntaxNode,
sourceCode: string,
Expand Down Expand Up @@ -274,6 +315,55 @@ function cleanUnusedRequireDeclarations(
return updatedSourceCode;
}

function cleanUnusedDynamicImportDeclarations(
rootNode: Parser.SyntaxNode,
sourceCode: string,
) {
let updatedSourceCode = sourceCode;

const dynamicImportDeclarations = getDynamicImportDeclarations(rootNode);

dynamicImportDeclarations.forEach((dynamicImportDeclaration) => {
const dynamicImportIdentifiers =
extractIdentifiersFromDynamicImportDeclaration(dynamicImportDeclaration);

const dynamicImportIdentifiersToRemove: Parser.SyntaxNode[] = [];

dynamicImportIdentifiers.forEach((dynamicImportIdentifier) => {
const isUsed = isIdentifierUsed(rootNode, dynamicImportIdentifier);
if (!isUsed) {
dynamicImportIdentifiersToRemove.push(dynamicImportIdentifier);
}
});

const removeDynamicImportDeclaration =
dynamicImportIdentifiersToRemove.length ===
dynamicImportIdentifiers.length;

if (removeDynamicImportDeclaration) {
updatedSourceCode = updatedSourceCode.replace(
sourceCode.substring(
dynamicImportDeclaration.startIndex,
dynamicImportDeclaration.endIndex + 1,
),
"",
);
}

dynamicImportIdentifiersToRemove.forEach((dynamicImportIdentifier) => {
updatedSourceCode = updatedSourceCode.replace(
sourceCode.substring(
dynamicImportIdentifier.startIndex,
dynamicImportIdentifier.endIndex + 1,
),
"",
);
});
});

return updatedSourceCode;
}

export function cleanupJavascriptFile(
parser: Parser,
sourceCode: string,
Expand Down Expand Up @@ -306,6 +396,18 @@ export function cleanupJavascriptFile(

tree = parser.parse(updatedSourceCode);

const resultAfterDynamicImportCleaning = removeInvalidFileDynamicImports(
tree.rootNode,
updatedSourceCode,
invalidDependencies,
);
updatedSourceCode = resultAfterDynamicImportCleaning.updatedSourceCode;
removedIdentifiers.push(
...resultAfterDynamicImportCleaning.removedIdentifiers,
);

tree = parser.parse(updatedSourceCode);

updatedSourceCode = removeDeletedImportUsage(
tree.rootNode,
updatedSourceCode,
Expand All @@ -326,5 +428,12 @@ export function cleanupJavascriptFile(
updatedSourceCode,
);

tree = parser.parse(updatedSourceCode);

updatedSourceCode = cleanUnusedDynamicImportDeclarations(
tree.rootNode,
updatedSourceCode,
);

return updatedSourceCode;
}
Loading

0 comments on commit 5feca2c

Please sign in to comment.