diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 49a73f7..bdbbf9a 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -16,9 +16,9 @@ jobs: # Dependencies - name: Install Dependencies run: npm ci - # Build + # Test - name: Build - run: npm run build + run: npm test # Lint - name: Lint run: npm run lint diff --git a/eslint.config.mjs b/eslint.config.mjs index ce32c2a..bd628c0 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,5 +1,4 @@ // @ts-check - import eslint from '@eslint/js'; import tseslint from 'typescript-eslint'; import * as path from 'node:path'; @@ -50,5 +49,13 @@ export default tseslint.config( 'build/', 'node_modules/' ] + }, + { + files: [ + 'src/test/**/*' + ], + rules: { + '@typescript-eslint/no-floating-promises': 'off' + } } ); \ No newline at end of file diff --git a/package.json b/package.json index 0276779..312a2c7 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,10 @@ "description": "", "main": "build/index.js", "scripts": { - "build": "tsc", + "build": "rm -rf build && tsc", "start": "npm run build && node build/index.js", - "lint": "eslint ." + "lint": "eslint .", + "test": "npm run build && node --test build/test" }, "author": "TheBrokenRail", "license": "MIT", diff --git a/src/common.ts b/src/common.ts index 765c834..99c0ca7 100644 --- a/src/common.ts +++ b/src/common.ts @@ -34,6 +34,7 @@ export function syntaxError(message?: string): never { throw new Error(extendErrorMessage(message, 'Syntax Error')); } // Convert 'int x' Into {type: 'int', name: 'x'} +const POINTER_TOKENS = ['*', '&']; export function parseTypeAndName(piece: string) { // Split On Last Space const index = piece.lastIndexOf(' '); @@ -43,10 +44,10 @@ export function parseTypeAndName(piece: string) { let name = piece.substring(index + 1); let type = piece.substring(0, index); // Move Asterisks From Name To Type - while (name.startsWith('*') || name.startsWith('&')) { + while (POINTER_TOKENS.some(x => name.startsWith(x))) { const x = name.substring(0, 1); name = name.substring(1); - if (!type.endsWith('*') && !type.endsWith('&')) { + if (!POINTER_TOKENS.some(x => type.endsWith(x))) { type += ' '; } type += x; @@ -79,7 +80,7 @@ export function toUpperSnakeCase(str: string) { } // Convert 'int' To 'int ' But Leave 'int *' As Is export function formatType(type: string) { - if (!type.endsWith('*') && !type.endsWith('&')) { + if (!POINTER_TOKENS.some(x => type.endsWith(x))) { type += ' '; } return type; diff --git a/src/test/common.ts b/src/test/common.ts new file mode 100644 index 0000000..d870920 --- /dev/null +++ b/src/test/common.ts @@ -0,0 +1,67 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { formatType, parseTypeAndName, prependArg, toUpperSnakeCase } from '../common'; + +describe('Parsing Variable Declarations', () => { + const tests: [string, string, string, string][] = [ + ['Simple', 'int x', 'int', 'x'], + ['Pointer', 'int *y', 'int *', 'y'], + ['Ugly Pointer', 'int* z', 'int*', 'z'], + ['Double Pointer', 'int **a', 'int **', 'a'], + ['Ugly Double Pointer', 'int* *b', 'int**', 'b'], + ['Reference', 'int &c', 'int &', 'c'], + ['Reference-To-Pointer', 'int *&d', 'int *&', 'd'] + ]; + for (const test of tests) { + it(test[0], () => { + const obj = parseTypeAndName(test[1]); + assert.strictEqual(obj.type, test[2]); + assert.strictEqual(obj.name, test[3]); + }); + } + it('Invalid', () => { + assert.throws(() => { + parseTypeAndName('abc'); + }); + }); +}); + +describe('Upper-Snake-Case', () => { + const tests: [string, string, string][] = [ + ['One Word', 'Hello', 'HELLO'], + ['Two Words', 'HelloWorld', 'HELLO_WORLD'], + ['Empty', '', ''], + ['All Uppercase', 'HELLO', 'HELLO'], + ['All Lowercase', 'hello', 'HELLO'] + ]; + for (const test of tests) { + it(test[0], () => { + assert.strictEqual(toUpperSnakeCase(test[1]), test[2]); + }); + } +}); + +describe('Formatting Types For Concatenation', () => { + const tests: [string, string, string][] = [ + ['Value', 'int', 'int '], + ['Pointer', 'int *', 'int *'], + ['Reference', 'int &', 'int &'] + ]; + for (const test of tests) { + it(test[0], () => { + assert.strictEqual(formatType(test[1]), test[2]); + }); + } +}); + +describe('Prepending Arguments To Functions', () => { + const tests: [string, string, string, string][] = [ + ['Empty', '()', 'int x', '(int x)'], + ['Non-Empty', '(int x)', 'int y', '(int y, int x)'] + ]; + for (const test of tests) { + it(test[0], () => { + assert.strictEqual(prependArg(test[1], test[2]), test[3]); + }); + } +}); \ No newline at end of file