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: define suggestColRefKeywords, suggestEngines, add typechecking ci #95

Merged
merged 3 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,24 @@ jobs:
- name: Lint Files
run: npm run lint

typecheck:
name: Typecheck files
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: '18.x'
cache: 'npm'
- name: Install Packages
run: npm ci
- name: Typecheck Files
run: npm run typecheck

generated:
name: Check generated files
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx nano-staged
npm run typecheck && npx nano-staged
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"lint": "npm run prettier -- --check",
"fix": "npm run prettier -- --write",
"prettier": "prettier \"**/*.{md,yaml,yml,json}\"",
"typecheck": "tsc --noEmit",
"build": "rimraf dist && tsc -p tsconfig.build.json",
"prepublishOnly": "npm run build"
},
Expand Down
6 changes: 3 additions & 3 deletions src/generator/lib/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,8 @@ export async function generateParsers(parserNames: string[]): Promise<void> {
throw new Error(`Could not find all requested parser definitions`);
}

for (let i = 0; i < parserDefinitions.length; i++) {
console.log(`Generating ${parserDefinitions[i].parserName}`);
await generateParser(parserDefinitions[i]);
for (const parserDefinition of parserDefinitions) {
console.log(`Generating ${parserDefinition.parserName}`);
await generateParser(parserDefinition);
}
}
23 changes: 23 additions & 0 deletions src/parsing/@types/expect.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Please note that the code below is the modified code distributed on the terms, mentioned below.
// The copyright for the changes belongs to YANDEX LLC.
//
// Copyright 2023 YANDEX LLC
//
// Licensed under the Apache License, Version 2.0 (the "License")
// You may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under
// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions
// and limitations under the License.

import type {Matchers} from 'expect';
import type { TestCase } from '../test/testing';

declare module 'expect' {
export interface Matchers<R extends void | Promise<void>, T = unknown> {
toEqualAutocompleteValues(values: string[]): R;
toEqualDefinition(testDefinition: TestCase): R;
}
}
15 changes: 15 additions & 0 deletions src/parsing/@types/jest.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Please note that the code below is the modified code distributed on the terms, mentioned below.
// The copyright for the changes belongs to YANDEX LLC.
//
// Copyright 2023 YANDEX LLC
//
// Licensed under the Apache License, Version 2.0 (the "License")
// You may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under
// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions
// and limitations under the License.

declare function fail(error?: any): never;
8 changes: 5 additions & 3 deletions src/parsing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export interface ParseResult {
suggestColumns?: ColumnSuggestion;
suggestAggregateFunctions?: AggregateFunctionsSuggestion;
suggestAnalyticFunctions?: unknown;
suggestColRefKeywords?: unknown;
suggestColRefKeywords?: {
[type: string]: string[];
};
suggestColumnAliases?: ColumnAliasSuggestion[];
suggestCommonTableExpressions?: unknown;
suggestDatabases?: DatabasesSuggestion;
Expand All @@ -26,8 +28,8 @@ export interface ParseResult {
suggestIdentifiers?: IdentifierSuggestion[];
suggestTemplates?: boolean;
suggestEngines?: {
engines: string[],
functionalEngines: string[]
engines: string[];
functionalEngines: string[];
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: I'd move two of those interfaces separately, like it's done in DatabasesSuggestion for example. We'll reuse those interfaces later in our tests anyway. But I can do it myself later


// Reasons for those fields are unknown
Expand Down
2 changes: 2 additions & 0 deletions src/parsing/lib/parsing-typed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
// either express or implied. See the License for the specific language governing permissions
// and limitations under the License.

import {expect} from '@jest/globals'

export interface CommonParser {
identifyPartials(
beforeCursor: string,
Expand Down
8 changes: 6 additions & 2 deletions src/parsing/lib/sql-reference/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,16 @@ export function matchesType(
const conversionTable = GENERIC_TYPE_CONVERSION;
for (let i = 0; i < expectedTypes.length; i++) {
for (let j = 0; j < actualTypes.length; j++) {
const expectedType = expectedTypes[i];
const actualType = actualTypes[j];
const convertedType = expectedType && conversionTable[expectedType];

// To support future unknown types
if (typeof conversionTable[expectedTypes[i]] === 'undefined' || typeof conversionTable[expectedTypes[i]][actualTypes[j]] == 'undefined') {
if (!convertedType || !actualType || typeof convertedType === 'undefined' || typeof convertedType[actualType] == 'undefined') {
return true;
}

if (conversionTable[expectedTypes[i]] && conversionTable[expectedTypes[i]][actualTypes[j]]) {
if (convertedType && convertedType[actualType]) {
return true;
}
}
Expand Down
1 change: 1 addition & 0 deletions src/parsing/test/jest.init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing permissions
// and limitations under the License.
import {expect} from '@jest/globals'

import {getToEqualAutocompleteValues, toEqualDefinition} from './testing';

Expand Down
95 changes: 58 additions & 37 deletions src/parsing/test/testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,34 @@
import {existsSync, readFileSync} from 'fs';
import {AutocompleteParser} from '../lib/types';
import {describe, expect, it, beforeAll} from '@jest/globals';
import type {ParseResult} from '..';

interface ExpectedError {
text: string,
token: string,
loc: {
first_line: number,
last_line: number,
first_column: number,
last_column: number
},
}

interface TestCase {
export interface TestCase {
namePrefix: string; // ex. "should suggest keywords"
beforeCursor: string;
afterCursor: string;
containsKeywords?: string[];
doesNotContainKeywords?: string[];
containsColRefKeywords?: string[];
containsColRefKeywords?: boolean | string[];
noErrors?: boolean;
locationsOnly?: boolean;
noLocations?: boolean;
expectedDefinitions: unknown,
expectedLocations?: unknown;
expectedLocations?: ParseResult['locations'];
expectedResult: {
lowerCase?: boolean;
locations?: unknown;
locations?: ParseResult['locations'];
suggestTables?: {
identifierChain?: { name: string }[];
onlyTables?: boolean;
Expand All @@ -57,30 +69,21 @@ interface TestCase {
};
suggestTemplates?: boolean;
};
expectedErrors?: {
text: string,
token: string,
loc: {
first_line: number,
last_line: number,
first_column: number,
last_column: number
},
}[]
expectedErrors?: ExpectedError[]
}

interface GroupedTestCases {
jisonFile: string;
testCases: TestCase[];
}

export function getToEqualAutocompleteValues(actualItems, expectedValues) {
export function getToEqualAutocompleteValues(actualItems: {value: string}[], expectedValues: string[]) {
if (actualItems.length !== expectedValues.length) {
return {pass: false, message: () => 'items length is not equal'};
}

for (let i = 0; i < expectedValues.length; i++) {
const stringValue = typeof actualItems[i] !== 'string' ? '' + actualItems[i].value : actualItems[i].value;
const stringValue = typeof actualItems[i] !== 'string' ? '' + actualItems[i]?.value : actualItems[i]?.value;
if (stringValue !== expectedValues[i]) {
return {
pass: false,
Expand All @@ -92,7 +95,7 @@ export function getToEqualAutocompleteValues(actualItems, expectedValues) {
return {pass: true, message: () => 'test'};
}

export function toEqualDefinition(actualResponse, testDefinition: TestCase) {
export function toEqualDefinition(actualResponse: Partial<ParseResult>, testDefinition: TestCase) {
if (typeof testDefinition.noErrors === 'undefined' && actualResponse.errors && !testDefinition.expectedErrors) {
let allRecoverable = true;
actualResponse.errors.forEach(error => {
Expand All @@ -119,7 +122,7 @@ export function toEqualDefinition(actualResponse, testDefinition: TestCase) {
) {
const expectedLoc =
testDefinition.expectedLocations || testDefinition.expectedResult.locations;
const expectsType = expectedLoc.some(location => location.type === 'statementType');
const expectsType = expectedLoc?.some(location => location.type === 'statementType');
if (!expectsType) {
actualResponse.locations = actualResponse.locations.filter(
location => location.type !== 'statementType'
Expand Down Expand Up @@ -152,20 +155,23 @@ export function toEqualDefinition(actualResponse, testDefinition: TestCase) {
}

if (actualResponse.suggestKeywords) {
const weightFreeKeywords = [];
const weightFreeKeywords: ParseResult['suggestKeywords'] = [];
actualResponse.suggestKeywords.forEach(keyword => {
weightFreeKeywords.push(keyword.value);
if (typeof keyword !== 'string') {
// @ts-ignore
NikitaShkaruba marked this conversation as resolved.
Show resolved Hide resolved
weightFreeKeywords.push(keyword.value);
}
});
actualResponse.suggestKeywords = weightFreeKeywords;
}

if (!!testDefinition.noLocations) {
if (actualResponse.locations.length > 0) {
if (actualResponse.locations && actualResponse.locations.length > 0) {
return {
pass: false,
message: constructTestCaseMessage(testDefinition, {
'Expected locations': 'none',
'Found locations': actualResponse.locations.length,
'Found locations': actualResponse.locations?.length,
}),
};
}
Expand All @@ -175,7 +181,9 @@ export function toEqualDefinition(actualResponse, testDefinition: TestCase) {
}
let deleteKeywords = false;
if (testDefinition.containsColRefKeywords) {
if (typeof actualResponse.suggestColRefKeywords == 'undefined') {
const actualSuggestColRefKeywords = actualResponse.suggestColRefKeywords;

if (typeof actualSuggestColRefKeywords == 'undefined') {
return {
pass: false,
message: constructTestCaseMessage(testDefinition, {
Expand All @@ -187,9 +195,9 @@ export function toEqualDefinition(actualResponse, testDefinition: TestCase) {
testDefinition.containsColRefKeywords.forEach(keyword => {
contains =
contains &&
(actualResponse.suggestColRefKeywords.BOOLEAN.indexOf(keyword) !== -1 ||
actualResponse.suggestColRefKeywords.NUMBER.indexOf(keyword) !== -1 ||
actualResponse.suggestColRefKeywords.STRING.indexOf(keyword) !== -1);
(actualSuggestColRefKeywords.BOOLEAN?.indexOf(keyword) !== -1 ||
actualSuggestColRefKeywords.NUMBER?.indexOf(keyword) !== -1 ||
actualSuggestColRefKeywords.STRING?.indexOf(keyword) !== -1);
});
if (!contains) {
return {
Expand All @@ -207,7 +215,8 @@ export function toEqualDefinition(actualResponse, testDefinition: TestCase) {
if (typeof testDefinition.containsKeywords !== 'undefined') {
const keywords = actualResponse.suggestKeywords;
let contains = true;
testDefinition.containsKeywords.forEach(keyword => {
testDefinition.containsKeywords.forEach((keyword): boolean | void => {
// @ts-ignore
if (typeof keywords === 'undefined' || keywords.indexOf(keyword) === -1) {
contains = false;
return false;
Expand All @@ -227,7 +236,8 @@ export function toEqualDefinition(actualResponse, testDefinition: TestCase) {
if (typeof testDefinition.doesNotContainKeywords !== 'undefined') {
const keywords = actualResponse.suggestKeywords || [];
let contains = false;
testDefinition.doesNotContainKeywords.forEach(keyword => {
testDefinition.doesNotContainKeywords.forEach((keyword): boolean | void => {
// @ts-ignore
if (typeof keywords === 'undefined' || keywords.indexOf(keyword) !== -1) {
contains = true;
return false;
Expand Down Expand Up @@ -276,9 +286,14 @@ export function toEqualDefinition(actualResponse, testDefinition: TestCase) {
}
}

const filteredResponseErrors = actualResponse.errors.map((responseError, index) => {
const filteredResponseErrors = actualResponse.errors.map((responseError: Record<string, any>, index) => {
if (!testDefinition.expectedErrors) {
return {};
}

// @ts-ignore
const expectedKeys = Object.keys(testDefinition.expectedErrors[index]);
return expectedKeys.reduce((acc, expectedKey) => {
return expectedKeys.reduce<Record<string, any>>((acc, expectedKey) => {
acc[expectedKey] = responseError[expectedKey];
return acc
}, {});
Expand Down Expand Up @@ -340,7 +355,7 @@ function constructTestCaseMessage(testCase: TestCase, details: Record<string, an
return () => message
}

function resultEquals(a, b): boolean {
function resultEquals(a: any, b: any): boolean {
if (typeof a !== typeof b) {
return false;
}
Expand All @@ -349,14 +364,20 @@ function resultEquals(a, b): boolean {
return true;
}

if (typeof a === 'object') {
if (typeof a === 'object' && a !== null) {
const aKeys = Object.keys(a);
if (aKeys.length !== Object.keys(b).length) {
return false;
}

for (let i = 0; i < aKeys.length; i++) {
if (!resultEquals(a[aKeys[i]], b[aKeys[i]])) {
const aKey = aKeys[i];

if (aKey === undefined) {
return false;
}

if (!resultEquals(a[aKey], b[aKey])) {
return false;
}
}
Expand All @@ -367,12 +388,12 @@ function resultEquals(a, b): boolean {
return a == b;
}

function jsonStringToJsString(jsonString) {
function jsonStringToJsString(jsonString: string): string {
return jsonString
.replace(/'([a-zA-Z]+)':/g, (all, group) => {
.replace(/'([a-zA-Z]+)':/g, (_, group) => {
return group + ':';
})
.replace(/([:{,])/g, (all, group) => {
.replace(/([:{,])/g, (_, group) => {
return group + ' ';
})
.replace(/[}]/g, ' }')
Expand Down
10 changes: 8 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@
"extends": "@gravity-ui/tsconfig",
"compilerOptions": {
"target": "ES2020",
"noErrorTruncation": true,
"resolveJsonModule": true,
"noUncheckedIndexedAccess": true,
"experimentalDecorators": false,
"strictNullChecks": true,
"noImplicitAny": true,
"checkJs": false,
"declaration": true,
"outDir": "dist",
"checkJs": false
"outDir": "dist"
},
"include": ["src/**/*"]
}
Loading