Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: empty string warning #36

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion .github/workflows/cypress-testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Run Cypress testing suite
on:
workflow_dispatch:
pull_request:
types: [ opened, synchronize ]
types: [opened, synchronize]

jobs:
cypress-run:
Expand Down
77 changes: 77 additions & 0 deletions .github/workflows/empty-string-warning.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Warn for Empty Strings

on:
pull_request:
paths:
- "**/*.ts"
- "**/*.tsx"

permissions:
issues: write
pull-requests: write

jobs:
check-empty-strings:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3
0x4007 marked this conversation as resolved.
Show resolved Hide resolved

- name: Find empty strings in TypeScript files
id: find_empty_strings
run: |
# Find empty strings and mark them explicitly
output=$(grep -Rn --include=\*.ts "''\|\"\"" --exclude-dir={node_modules,dist,out} . || true)

if [ -z "$output" ]; then
echo "::set-output name=results::No empty strings found."
exit 0
else
output=$(echo "$output" | sed 's/""/"[EMPTY STRING]"/g' | sed "s/''/'[EMPTY STRING]'/g")
echo "::set-output name=results::${output//$'\n'/%0A}"
fi

- name: findings
if: steps.find_empty_strings.outputs.results != 'No empty strings found.'
run: |
if [ "${{ steps.find_empty_strings.outputs.results }}" == "No empty strings found." ]; then
echo "No empty strings found. No action required."
else
echo "::warning::Empty strings found in the following files:"
echo "${{ steps.find_empty_strings.outputs.results }}"
fi

- name: Find Comment
uses: peter-evans/find-comment@v3
id: find_comment
with:
issue-number: ${{ github.event.pull_request.number }}
body-includes: Empty String Usage Detected
comment-author: github-actions[bot]

- name: Comment on PR
if: steps.find_empty_strings.outputs.results != 'No empty strings found.'
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.pull_request.number }}
comment-id: ${{ steps.find_comment.outputs.comment-id }}
edit-mode: replace
body: |
## Empty String Usage Detected

The following occurrences of empty strings were detected. Please review them to ensure their usage is necessary:

```plaintext
${{ steps.find_empty_strings.outputs.results }}
```

- name: Remove comment
if: steps.find_empty_strings.outputs.results == 'No empty strings found.'
uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
const { number } = context.issue;
const comment_id = ${{ steps.find_comment.outputs.comment-id }}
github.rest.issues.deleteComment({ owner, repo, comment_id });
Keyrxng marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 2 additions & 2 deletions .github/workflows/jest-testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Run Jest testing suite
on:
workflow_dispatch:
pull_request_target:
types: [ opened, synchronize ]
types: [opened, synchronize]

env:
NODE_ENV: "test"
Expand All @@ -14,7 +14,7 @@ jobs:
steps:
- uses: actions/setup-node@v4
with:
node-version: '20.10.0'
node-version: "20.10.0"
- uses: actions/checkout@master
with:
fetch-depth: 0
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v4
with:
node-version: '20.10.0'
node-version: "20.10.0"
registry-url: https://registry.npmjs.org/
- run: |
yarn install --immutable --immutable-cache --check-cache
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,23 @@ This template repository includes support for the following:
## Testing

### Cypress

To test with Cypress Studio UI, run

```shell
yarn cy:open
```

Otherwise to simply run the tests through the console, run

```shell
yarn cy:run
```

### Jest

To start Jest tests, run

```shell
yarn test
```
60 changes: 60 additions & 0 deletions tests/workflows/empty-strings/empty-strings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { expect, describe, beforeEach, it } from "@jest/globals";
import { CheckEmptyStringsOptions, checkForEmptyStringsInFiles } from "./empty-strings";

const mockReadDir = jest.fn();
const mockReadFile = jest.fn();

const options: CheckEmptyStringsOptions = {
readDir: mockReadDir,
readFile: mockReadFile,
ignorePatterns: [],
};

describe("checkForEmptyStringsInFiles", () => {
beforeEach(() => {
jest.clearAllMocks();
});

it("identifies files with empty strings", async () => {
mockReadDir.mockResolvedValue(["file1.ts", "file2.ts"]);
mockReadFile.mockImplementation((path) => {
if (path.includes("file1.ts")) {
return Promise.resolve('const a = "";');
}
return Promise.resolve('const b = "notEmpty";');
});

const result = await checkForEmptyStringsInFiles(".", options);
expect(result).toEqual(["file1.ts"]);
});

it("ignores files based on ignorePatterns and .gitignore", async () => {
options.ignorePatterns = ["file2.ts"];
mockReadDir.mockResolvedValue(["file1.ts", "file2.ts", "file3.ts"]);
mockReadFile.mockImplementation((path) => {
if (path === ".gitignore") {
return Promise.resolve("file3.ts");
} else if (path.includes("file1.ts")) {
return Promise.resolve('const a = "";');
}
return Promise.resolve('const b = "notEmpty";');
});

const result = await checkForEmptyStringsInFiles(".", options);
expect(result).toEqual(["file1.ts"]);
});

it("returns an empty array when no empty strings are found", async () => {
mockReadDir.mockResolvedValue(["file1.ts"]);
mockReadFile.mockResolvedValue('const a = "notEmpty";');

const result = await checkForEmptyStringsInFiles(".", options);
expect(result).toHaveLength(0);
});

it("handles errors gracefully", async () => {
mockReadDir.mockRejectedValue(new Error("Error reading directory"));

await expect(checkForEmptyStringsInFiles(".", options)).resolves.toEqual([]);
});
});
45 changes: 45 additions & 0 deletions tests/workflows/empty-strings/empty-strings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
export interface CheckEmptyStringsOptions {
readDir: (path: string) => Promise<string[]>;
readFile: (path: string, encoding: string) => Promise<string>;
ignorePatterns?: string[];
}

export async function checkForEmptyStringsInFiles(dir: string, options: CheckEmptyStringsOptions): Promise<string[]> {
const { readDir, readFile, ignorePatterns = [] } = options;
const filesWithEmptyStrings: string[] = [];
const ignoreList: string[] = ["^\\.\\/\\.git", "^\\.\\/\\..*", "^\\.\\/node_modules", "^\\.\\/dist", "^\\.\\/out", ".*\\.test\\.ts$", ...ignorePatterns];

try {
const gitignoreContent = await readFile(".gitignore", "utf8");
gitignoreContent.split("\n").forEach((line) => {
if (line.trim() !== "") {
ignoreList.push(`^\\.\\/${line.replace(/\./g, "\\.").replace(/\//g, "\\/")}`);
}
});
} catch (error) {
console.error("Error reading .gitignore file:", error);
}

try {
const files = await readDir(dir);
for (const fileName of files) {
let shouldIgnore = false;
for (const pattern of ignoreList) {
if (new RegExp(pattern).test(fileName)) {
shouldIgnore = true;
break;
}
}
if (shouldIgnore || !fileName.endsWith(".ts")) continue;

const fileContent = await readFile(`${dir}/${fileName}`, "utf8");
if (fileContent.includes('""') || fileContent.includes("''")) {
filesWithEmptyStrings.push(fileName);
}
}
} catch (error) {
console.error("Error reading directory or file contents:", error);
}

return filesWithEmptyStrings;
}
3 changes: 3 additions & 0 deletions tests/workflows/empty-strings/test-function.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function testFunction() {
return "";
}
6 changes: 6 additions & 0 deletions tests/workflows/empty-strings/test.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
test: {
test: "",
},
tester: "",
};
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@

/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
"resolveJsonModule": true
}
}
Loading