Skip to content

Commit

Permalink
Fix broken parse-literal-occurences on syntax errored code (#43)
Browse files Browse the repository at this point in the history
* fix broken parse-literal-occurences on syntax errored code

* upgrade to 1.5.1
  • Loading branch information
Chnapy committed Jan 31, 2023
1 parent 9921fe0 commit f16f6ba
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 98 deletions.
53 changes: 0 additions & 53 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
Binary file not shown.
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ts-gql-plugin",
"version": "1.5.0",
"version": "1.5.1",
"packageManager": "[email protected]",
"license": "MIT",
"main": "./dist/index.js",
Expand Down Expand Up @@ -41,7 +41,6 @@
"@graphql-codegen/core": "2.6.8",
"@graphql-codegen/typescript": "2.8.8",
"@graphql-codegen/typescript-operations": "2.5.13",
"@graphql-tools/graphql-tag-pluck": "7.4.3",
"deasync": "0.1.28",
"graphql-config": "4.4.0",
"graphql-language-service": "5.1.1",
Expand Down
8 changes: 4 additions & 4 deletions src/language-service/get-quick-info-at-position.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ export const getQuickInfosPayload = async (

const occurences = parseLiteralOccurenceList(sourceFile);

const target = occurences.find(({ locationOffset, body }) => {
if (locationOffset.index === undefined) {
const target = occurences.find(({ index, body }) => {
if (index === undefined) {
return false;
}

const start = locationOffset.index;
const start = index;
const end = start + body.length;

return position >= start && position <= end;
Expand All @@ -53,7 +53,7 @@ export const getQuickInfosPayload = async (
return null;
}

const literalPosition = position - target.locationOffset.index! - 1;
const literalPosition = position - target.index! - 1;
const lines = targetLiteral.slice(0, literalPosition + 1).split('\n');
const currentLine = lines[lines.length - 1];

Expand Down
93 changes: 93 additions & 0 deletions src/source-update/parse-literal-occurence-list.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,99 @@ export const CartList: React.FC = () => {
);
};
export default CartList;
`;

expect(
parseLiteralOccurenceList(createSourceFile(code)).map((source) =>
formatGQL(source.body)
)
).toEqual([
formatGQL(`
query User($id: ID!) {
user(id: $id) {
id
name
}
users {
id
email
}
}
`),
]);
});

it('ignore syntax errors', () => {
const code = `
import { VendorProductListItem } from 'web-client/components/organisms';
import { CartItemRemoved } from 'web-client/components/organisms/cart/cartList/CartItemRemoved';
import { useCart } from 'web-client/components/organisms/cart/hooks/UseCart';
import { ProductFromVendorProductContextProvider } from 'web-client/components/organisms/product/context/ProductContext';
import { VendorProductContextProvider } from 'web-client/components/organisms/product/context/VendorProductContext';
import { useTranslate } from '@reparcar/translation';
import React from 'react';
import { IRIUtils } from '@reparcar/common';
import BottomText from '../bottomText/BottomText';
import { EmptyCart } from '../emptyCart/EmptyCart';
import styles from './CartList.module.scss';
import { useOrderCartItemDeleteMutation, refetchOrderCartQuery } from '@reparcar/network-order';
import { useGraphQLArray } from '@reparcar/network-utils';
import { Spinner } from '@reparcar/ui';
import { useCartPrice } from 'web-client/components/organisms/cart/hooks/UseCartPrice';
export const CartList: React.FC = () => {
const { t } = useTranslate();
const { data: cartData, loading } = useQuery(gql(\`
query User($id: ID!) {
user(id: $id) {
id
name
}
users {
id
email
}
}
\`));
const cartItems = useGraphQLArray(cartData?.cartItems);
\`
const [deleteCartItem] = useOrderCartItemDeleteMutation(
gql(\`
mutation UserDelete($id: ID!) {
user(id: $id) {
id
name
}
}
\`), {
refetchQueries: [
refetchOrderCartQuery({
token: cartData?.token,
}),
],
awaitRefetchQueries: true,
});
const [removedVendorProductId, setRemovedVendorProductId] = React.useState<string | null>(null);
const cartPrice = useCartPrice();
return (
<div>
{removedVendorProductId && (
<VendorProductContextProvider value={removedVendorProductId}>
<ProductFromVendorProductContextProvider>
<CartItemRemoved />
</ProductFromVendorProductContextProvider>
</VendorProductContextProvider>
)}
{loading ? <Spinner /> : <div>foo</div>}
<BottomText />
</div>
);
};
export default CartList;
`;

Expand Down
58 changes: 51 additions & 7 deletions src/source-update/parse-literal-occurence-list.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,55 @@
import ts from 'typescript';
import { gqlPluckFromCodeStringSync } from '@graphql-tools/graphql-tag-pluck';
import { Source } from 'graphql';

type SourceWithIndex = Source & {
locationOffset: Source['locationOffset'] & { index?: number };
type SourceWithIndex = {
body: string;
index?: number;
};

const expectedTags: ReadonlySet<string> = new Set(['gql', 'graphql']);

/**
* Parse source file and extract every valid gql template literals from it.
* Use TS api tree visitor, which works even on syntax broken code.
*/
const parseLiteralOccurenceListWithTS = (
sourceFile: ts.SourceFile
): SourceWithIndex[] => {
const getText = (node: ts.Node) => node.getText(sourceFile);

const extractTemplateLiterals = (
node: ts.Node,
parentIsAsExpression = false
): ts.TemplateLiteral[] => {
const isAsExpression = ts.isAsExpression(node);

const next = () =>
node
.getChildren(sourceFile)
.flatMap((child) => extractTemplateLiterals(child, isAsExpression));

if (!parentIsAsExpression && ts.isCallExpression(node)) {
const tag = getText(node.expression);
if (!expectedTags.has(tag)) {
return next();
}

const templateLiteral = node.arguments.find(ts.isTemplateLiteral);
if (!templateLiteral) {
return next();
}

return [templateLiteral];
}

return next();
};

return extractTemplateLiterals(sourceFile).map(
(templateLiteral): SourceWithIndex => ({
body: getText(templateLiteral).slice(1, -1),
index: templateLiteral.pos,
})
);
};

/**
Expand All @@ -17,7 +63,5 @@ export const parseLiteralOccurenceList = (
return [];
}

return gqlPluckFromCodeStringSync(sourceFile.fileName, sourceFile.text, {
skipIndent: true,
});
return parseLiteralOccurenceListWithTS(sourceFile);
};
36 changes: 4 additions & 32 deletions yarn.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 comment on commit f16f6ba

@github-actions
Copy link

Choose a reason for hiding this comment

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

"with ts-gql-plugin" vs "without ts-gql-plugin" Benchmark

Benchmark suite Current: f16f6ba Previous: 9921fe0 Ratio
performance impact %: "with ts-gql-plugin" vs "without ts-gql-plugin" 21.87 % (±1.83%) 24.87 % (±1.46%) 1.14

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.