Skip to content

Commit de58e27

Browse files
authored
add @save annotation support for variable declaration statements (#68)
This PR extends the `@save` annotation functionality to variables & constants Algorithm: 1. Get all variable/constant declarations ([link](6defceb#diff-21ac06960a99cc021392aabfef047efd6b0aa04eff1dbdc351fc10602d195421)) 2. Preserve saved variables & constants([link](dc21245#diff-3e1cfc841d953f0811ae81c0a75b5484a5596ba9c3e80d6396f69c7954cfaec1))
2 parents f542da7 + 8284efe commit de58e27

File tree

21 files changed

+1246
-22
lines changed

21 files changed

+1246
-22
lines changed

changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Extend @save annotation support for variable declaration statements ([#68](https://github.com/hasura/ndc-open-api-lambda/pull/68))
6+
57
## [[1.1.0](https://github.com/hasura/ndc-open-api-lambda/releases/tag/v1.1.0)] 2024-11-28
68

79
- Read the value of `NDC_OAS_BASE_URL` at runtime instead of build time ([#66](https://github.com/hasura/ndc-open-api-lambda/pull/66))

docs/documentation.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ This connector is published as a Docker Image. The image name is `ghcr.io/hasura
1414

1515
### Saving User Changes
1616

17-
When re-introspecting the connector, user changes in `functions.ts` can be preserved by adding an `@save` JS Doc Tag to the documentation comment of a function. This will ensure that that function is not overwritten and the saved function will be added if missing in the newly generated `functions.ts`
17+
When re-introspecting the connector, user changes in `functions.ts` can be preserved by adding an `@save` JS Doc Tag to the documentation comment of a statement. `@save` is currently supported for the following statements:
18+
- functions
19+
- variable/constant declarations.
20+
21+
This will ensure that the statements marked with `@save` are not overwritten and the saved statements will be added if missing in the newly generated `functions.ts`
1822

1923
Example
2024

@@ -26,6 +30,12 @@ Example
2630
function mutateResponse(response: ApiResponseObject) {
2731
response.description = "This API does some work. I hope that's helpful";
2832
}
33+
34+
/**
35+
* localhost url for testing APIs
36+
* @save
37+
*/
38+
const localApiBaseUrl = "http://localhost:8080"
2939
```
3040

3141
### Usage without the DDN CLI

src/app/parser/typescript/index.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,38 @@ export function preserveUserChanges(
77
staleContent: string,
88
freshContent: string,
99
): string {
10-
const staleProject = new ts.Project();
10+
let staleProject = new ts.Project();
1111

12-
const staleSourceFile = staleProject.createSourceFile(
12+
let staleSourceFile = staleProject.createSourceFile(
1313
path.resolve(context.getInstance().getOutputDirectory(), "stale.ts"),
1414
staleContent,
1515
);
1616

17-
const freshProject = new ts.Project();
18-
const freshSourceFile = freshProject.createSourceFile(
17+
let freshProject = new ts.Project();
18+
let freshSourceFile = freshProject.createSourceFile(
1919
path.resolve(context.getInstance().getOutputDirectory(), "fresh.ts"),
2020
freshContent,
2121
);
2222

2323
morph.preserveSavedFunctions(staleSourceFile, freshSourceFile);
2424

25+
// recalcuate nodes
26+
staleContent = staleSourceFile.getFullText();
27+
freshContent = freshSourceFile.getFullText();
28+
29+
staleProject = new ts.Project();
30+
staleSourceFile = staleProject.createSourceFile(
31+
path.resolve(context.getInstance().getOutputDirectory(), "stale.ts"),
32+
staleContent,
33+
);
34+
35+
freshProject = new ts.Project();
36+
freshSourceFile = freshProject.createSourceFile(
37+
path.resolve(context.getInstance().getOutputDirectory(), "fresh.ts"),
38+
freshContent,
39+
);
40+
41+
morph.preserveSavedVariables(staleSourceFile, freshSourceFile);
42+
2543
return freshSourceFile.getFullText();
2644
}

src/app/parser/typescript/morph.test.ts

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,38 +13,63 @@ type TestCase = {
1313
mergedFileContents?: string;
1414
};
1515

16-
const tests: TestCase[] = [
16+
const functionTests: TestCase[] = [
1717
{
1818
name: "Add and Replace",
19-
directory: "./test-data/morph-tests/add-and-replace/",
19+
directory: "./test-data/morph-tests/functions/add-and-replace/",
2020
},
2121
{
2222
name: "No change",
23-
directory: "./test-data/morph-tests/no-change/",
23+
directory: "./test-data/morph-tests/functions/no-change/",
2424
},
2525
];
2626

2727
describe("morph::preserveSavedFunctions", async () => {
28-
for (const testCase of tests) {
28+
for (const testCase of functionTests) {
2929
before(function () {
30-
testCase.directory = path.resolve(__dirname, testCase.directory);
31-
const staleProject = new ts.Project();
32-
testCase.staleSourceFile = staleProject.addSourceFileAtPath(
33-
path.resolve(testCase.directory, "stale.ts"),
30+
setupTest(testCase);
31+
});
32+
33+
it(testCase.name, async () => {
34+
morph.preserveSavedFunctions(
35+
testCase.staleSourceFile!,
36+
testCase.freshSourceFile!,
3437
);
3538

36-
const freshProject = new ts.Project();
37-
testCase.freshSourceFile = freshProject.addSourceFileAtPath(
38-
path.resolve(testCase.directory, "fresh.ts"),
39+
const gotStr = await prettier.format(
40+
testCase.freshSourceFile?.getFullText()!,
41+
{
42+
parser: "typescript",
43+
},
3944
);
4045

41-
testCase.mergedFileContents = fs
42-
.readFileSync(path.resolve(testCase.directory, "merged.ts"))
43-
.toString();
46+
assert.equal(testCase.mergedFileContents, gotStr);
47+
48+
// uncomment to update merged golden file
49+
// fs.writeFileSync(path.resolve(testCase.directory, "merged.ts"), gotStr);
50+
});
51+
}
52+
});
53+
54+
const variableTests: TestCase[] = [
55+
{
56+
name: "Add and Replace",
57+
directory: "./test-data/morph-tests/variables/add-and-replace/",
58+
},
59+
{
60+
name: "No change",
61+
directory: "./test-data/morph-tests/variables/no-change/",
62+
},
63+
];
64+
65+
describe("morph::preserveSavedVariables", async () => {
66+
for (const testCase of variableTests) {
67+
before(function () {
68+
setupTest(testCase);
4469
});
4570

4671
it(testCase.name, async () => {
47-
morph.preserveSavedFunctions(
72+
morph.preserveSavedVariables(
4873
testCase.staleSourceFile!,
4974
testCase.freshSourceFile!,
5075
);
@@ -63,3 +88,20 @@ describe("morph::preserveSavedFunctions", async () => {
6388
});
6489
}
6590
});
91+
92+
function setupTest(testCase: TestCase) {
93+
testCase.directory = path.resolve(__dirname, testCase.directory);
94+
const staleProject = new ts.Project();
95+
testCase.staleSourceFile = staleProject.addSourceFileAtPath(
96+
path.resolve(testCase.directory, "stale.ts"),
97+
);
98+
99+
const freshProject = new ts.Project();
100+
testCase.freshSourceFile = freshProject.addSourceFileAtPath(
101+
path.resolve(testCase.directory, "fresh.ts"),
102+
);
103+
104+
testCase.mergedFileContents = fs
105+
.readFileSync(path.resolve(testCase.directory, "merged.ts"))
106+
.toString();
107+
}

src/app/parser/typescript/morph.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,35 @@ export function preserveSavedFunctions(
3636
}
3737
});
3838
}
39+
40+
/**
41+
* this function preserves @save variables from a stale source file to a new/fresh source file
42+
* @param staleTsSourceFile the old TS source, which has the @save variables that should be preserved
43+
* @param freshTsSourceFile the new TS source, to which the @save variables will copied over to
44+
*/
45+
export function preserveSavedVariables(
46+
staleTsSourceFile: tsMorph.SourceFile,
47+
freshTsSourceFile: tsMorph.SourceFile,
48+
) {
49+
const staleSourceVariables = walk.getAllVariablesMap(staleTsSourceFile);
50+
const freshSourceVariables = walk.getAllVariablesMap(freshTsSourceFile);
51+
52+
staleSourceVariables.forEach((staleVariable, staleVariableName) => {
53+
if (!walk.isNodeSaved(staleVariable)) {
54+
return;
55+
}
56+
57+
const freshVariable = freshSourceVariables.get(staleVariableName);
58+
59+
// case 1: @save variable exists in the fresh source file
60+
if (freshVariable) {
61+
// in this case, the variable should be replaced with the stale variable
62+
freshVariable.replaceWithText(staleVariable.getFullText());
63+
}
64+
else {
65+
// case 2: @save variable does not exist in the new/fresh ts source file
66+
// in this case, we add the @save variable to the fresh ts source file
67+
freshTsSourceFile.addStatements(staleVariable.getFullText());
68+
}
69+
});
70+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import path from "path";
2+
3+
// ------ const tests ------
4+
5+
const constSavedApi = {
6+
baseUrl: "hello world!",
7+
params: {},
8+
};
9+
10+
const constApi = {
11+
baseUrl: "https://production.my-api.com",
12+
params: {
13+
headers: {
14+
"Content-Type": "application/xml",
15+
},
16+
},
17+
};
18+
19+
const constNumbers: number[] = [1, 22, 678, 4, 500000];
20+
21+
const constSavedNumbers: number[] = [0, 0, 456];
22+
23+
// ------ var tests ------
24+
25+
var varSavedApi = {
26+
baseUrl: "hello world!",
27+
params: {},
28+
};
29+
30+
var varApi = {
31+
baseUrl: "https://production.my-api.com",
32+
params: {
33+
headers: {
34+
"Content-Type": "application/xml",
35+
},
36+
},
37+
};
38+
39+
var varNumbers: number[] = [1, 22, 678, 4, 500000];
40+
41+
var varSavedNumbers: number[] = [0, 0, 456];
42+
43+
// ------ let tests ------
44+
45+
let letSavedApi = {
46+
baseUrl: "hello world!",
47+
params: {},
48+
};
49+
50+
let letApi = {
51+
baseUrl: "https://production.my-api.com",
52+
params: {
53+
headers: {
54+
"Content-Type": "application/xml",
55+
},
56+
},
57+
};
58+
59+
let letNumbers: number[] = [1, 22, 678, 4, 500000];
60+
61+
let letSavedNumbers: number[] = [0, 0, 456];

0 commit comments

Comments
 (0)