Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
31 changes: 31 additions & 0 deletions .github/workflows/formatting-integrity-check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
on:
push:
branches:
- main
paths:
- 'packages/language-support/src/formatting/**'

pull_request:
branches:
- main
paths:
- 'packages/language-support/src/formatting/**'

jobs:
formatting-integrity-check:
name: Formatter integrity check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup antlr4
uses: ./.github/actions/setup-antlr4

- name: Install dependencies with frozen lock file and generate parser
run: npm ci

- name: Build Project
run: npm run build

- name: Run formatting check
run: npm run test:formattingIntegrity
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"watch": "turbowatch ./turbowatch.ts",
"test": "turbo run test",
"test:e2e": "turbo run test:e2e",
"test:formattingIntegrity": "ts-node ./packages/language-support/src/tests/formatting/verification/verificationCheck.ts",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if there's a more idiomatic way to run a file as a script? Or maybe it shouldn't be run as a script at all, maybe there's a better way? Happy to take any suggestions here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other cases, I've made the script build js and then execute it. We've also needed to use cross-env for some cases (see the gen-parser script, although being on mac I don't really know why it's needed 😅 )

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the record I think it's fine to leave as is here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cross-env is only to use environment variables (we cannot set them for builds on Windows otherwise)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we move to node22 we can run it without ts-node as well with --experimental-strip-types

"dev-codemirror": "turbo run @neo4j-cypher/react-codemirror-playground#dev",
"lint": "eslint . --ext .ts",
"lint-fix": "eslint . --fix --ext .ts",
Expand Down
11,935 changes: 11,935 additions & 0 deletions packages/language-support/src/tests/formatting/verification/sample_queries.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as fs from 'fs';
import * as path from 'path';
import { formatQuery } from '../../../formatting/formatting';
import { standardizeQuery } from '../../../formatting/standardizer';

function throwError(message: string, query: string, formatted: string): never {
throw new Error(`${message},
--------- QUERY BEFORE START ------------
${query}
--------- QUERY BEFORE END ----------
--------- QUERY FORMATTED START ------------
${formatted}
--------- QUERY FORMATTED END ----------
Comment on lines +8 to +14
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This leaves some pretty nice output which should make it easy to debug, see this one for instance:

https://github.com/neo4j/cypher-language-support/actions/runs/13951159540/job/39050765538

`);
}

function verifyFormatting(query: string): void {
const formatted = formatQuery(query);
const queryStandardized = standardizeQuery(query);
const formattedStandardized = standardizeQuery(formatted);
const originalNonWhitespaceCount = query.replace(/\s/g, '').length;
const formattedNonWhitespaceCount = formatted.replace(/\s/g, '').length;

// Non-whitespace character count check
if (originalNonWhitespaceCount !== formattedNonWhitespaceCount) {
throwError('Non-whitespace character count mismatch', query, formatted);
}

// AST integrity check
if (formattedStandardized !== queryStandardized) {
throwError(
'Standardized query does not match standardized formatted query',
query,
formatted,
);
}

// Idempotency check
const formattedTwice = formatQuery(formatted);
if (formattedTwice !== formatted) {
throwError('Formatting is not idempotent', query, formatted);
}
}

function verifyFormattingOfSampleQueries() {
const filePath = path.join(__dirname, 'sample_queries.json');
const fileContent = fs.readFileSync(filePath, 'utf-8');
const queries: string[] = JSON.parse(fileContent) as string[];
queries.forEach((query) => verifyFormatting(query));
}

verifyFormattingOfSampleQueries();