Skip to content

Commit

Permalink
Merge pull request #1287 from Quramy/use_provided_typescript_module
Browse files Browse the repository at this point in the history
chore: Use provided TypeScript module
  • Loading branch information
Quramy committed Apr 2, 2024
2 parents 21529b7 + 40b42e0 commit 39c3bf5
Show file tree
Hide file tree
Showing 33 changed files with 163 additions and 120 deletions.
11 changes: 11 additions & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,21 @@ rules:
no-duplicate-imports: error
no-var: error
no-unsafe-finally: error
no-restricted-imports: off
prefer-const: error
prefer-rest-params: error
no-trailing-spaces:
- error
- ignoreComments: true
'@typescript-eslint/no-use-before-define': error
'@typescript-eslint/no-namespace': error
overrides:
- files: '**/*.ts'
excludedFiles: ['*.test.ts', '**/testing/**']
rules:
'@typescript-eslint/no-restricted-imports':
- error
- paths:
- name: typescript
message: "Use 'tsmodule' instead"
allowTypeImports: true
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"prepare": "husky install",
"clean": "rimraf -g lib \"e2e/*.log\" \"*.tsbuildinfo\"",
"build": "run-s build:ts build:doc",
"build:ts": "tsc -p .",
"build:ts": "tsc -p . && cp src/tsmodule.js lib && cp src/tsmodule.d.ts lib",
"build:doc": "npm run doc:toc",
"lint": "eslint \"src/**/*.{ts,tsx}\"",
"jest": "jest",
Expand Down
2 changes: 1 addition & 1 deletion src/analyzer/analyzer-factory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path from 'node:path';
import ts from 'typescript';
import ts from '../tsmodule';
import { Analyzer } from './analyzer';
import type { TsGraphQLPluginConfigOptions } from '../types';
import { ScriptHost } from '../ts-ast-util';
Expand Down
4 changes: 3 additions & 1 deletion src/analyzer/analyzer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import path from 'node:path';
import ts from 'typescript';

import ts from '../tsmodule';

import type { ScriptSourceHelper } from '../ts-ast-util/types';
import { Extractor } from './extractor';
import {
Expand Down
2 changes: 1 addition & 1 deletion src/analyzer/extractor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ts from 'typescript';
import type ts from '../tsmodule';
import {
parse,
visit,
Expand Down
3 changes: 2 additions & 1 deletion src/analyzer/type-generator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import path from 'node:path';
import ts from 'typescript';
import type { GraphQLSchema } from 'graphql';

import ts from '../tsmodule';

import { TsGqlError, ErrorWithLocation } from '../errors';
import {
mergeAddons,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type ts from 'typescript';
import type ts from '../tsmodule';
import { getAutocompleteSuggestions, type CompletionItem } from 'graphql-language-service';
import type { AnalysisContext, GetCompletionAtPosition } from './types';
import { SimplePosition } from './simple-position';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ts from 'typescript';
import { visit, type FragmentSpreadNode } from 'graphql';
import ts from '../tsmodule';
import { getSanitizedTemplateText } from '../ts-ast-util';
import type { AnalysisContext, GetDefinitionAndBoundSpan } from './types';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ts from 'typescript';
import { getHoverInformation } from 'graphql-language-service';

import ts from '../tsmodule';
import type { AnalysisContext, GetQuickInfoAtPosition } from './types';
import { SimplePosition } from './simple-position';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import ts from 'typescript';
import type { FragmentDefinitionNode } from 'graphql';
import { getDiagnostics, type Diagnostic } from 'graphql-language-service';

import ts from '../tsmodule';

import { SchemaBuildErrorInfo } from '../schema-manager/schema-manager';
import { ERROR_CODES } from '../errors';
import type { AnalysisContext, GetSemanticDiagnostics } from './types';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ts from 'typescript';
import { parse, type GraphQLSchema, type DocumentNode } from 'graphql';

import ts from '../tsmodule';
import {
getTemplateNodeUnder,
isTaggedTemplateNode,
Expand Down
2 changes: 1 addition & 1 deletion src/graphql-language-service-adapter/simple-position.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { LineAndCharacter } from 'typescript';
import type { LineAndCharacter } from '../tsmodule';
import type { IPosition } from 'graphql-language-service';

export class SimplePosition implements IPosition {
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { pluginModuleFactory } from './language-service-plugin';

export {
export type {
TypeGenAddonFactory,
TypeGenVisitorAddon,
TypeGenVisitorAddonContext,
Expand Down
85 changes: 85 additions & 0 deletions src/language-service-plugin/create-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import type ts from 'typescript/lib/tsserverlibrary';
import type { TsGraphQLPluginConfigOptions } from '../types';
import { GraphQLLanguageServiceAdapter } from '../graphql-language-service-adapter';
import { SchemaManagerFactory, createSchemaManagerHostFromLSPluginInfo } from '../schema-manager';
import { FragmentRegistry } from '../gql-ast-util';
import {
createScriptSourceHelper,
registerDocumentChangeEvent,
getTemplateNodeUnder,
findAllNodes,
getSanitizedTemplateText,
createFileNameFilter,
parseTagConfig,
} from '../ts-ast-util';
import { LanguageServiceProxyBuilder } from './language-service-proxy-builder';

export function create(info: ts.server.PluginCreateInfo): ts.LanguageService {
const logger = (msg: string) => info.project.projectService.logger.info(`[ts-graphql-plugin] ${msg}`);
logger('config: ' + JSON.stringify(info.config));
const schemaManager = new SchemaManagerFactory(createSchemaManagerHostFromLSPluginInfo(info)).create();
const { schema, errors: schemaErrors } = schemaManager.getSchema();
const config = info.config as TsGraphQLPluginConfigOptions;
const tag = parseTagConfig(config.tag);
const removeDuplicatedFragments = config.removeDuplicatedFragments === false ? false : true;
const enabledGlobalFragments = config.enabledGlobalFragments !== false;
const isExcluded = createFileNameFilter({ specs: config.exclude, projectName: info.project.getProjectName() });

const fragmentRegistry = new FragmentRegistry({ logger });
if (enabledGlobalFragments) {
const handleAcquireOrUpdate = (fileName: string, sourceFile: ts.SourceFile, version: string) => {
fragmentRegistry.registerDocuments(
fileName,
version,
findAllNodes(sourceFile, node => getTemplateNodeUnder(node, tag)).map(node =>
getSanitizedTemplateText(node, sourceFile),
),
);
};

registerDocumentChangeEvent(
// Note:
// documentRegistry in ts.server.Project is annotated @internal
(info.project as any).documentRegistry as ts.DocumentRegistry,
{
onAcquire: (fileName, sourceFile, version) => {
if (!isExcluded(fileName) && info.languageServiceHost.getScriptFileNames().includes(fileName)) {
handleAcquireOrUpdate(fileName, sourceFile, version);
}
},
onUpdate: (fileName, sourceFile, version) => {
if (!isExcluded(fileName) && info.languageServiceHost.getScriptFileNames().includes(fileName)) {
if (fragmentRegistry.getFileCurrentVersion(fileName) === version) return;
handleAcquireOrUpdate(fileName, sourceFile, version);
}
},
onRelease: fileName => {
fragmentRegistry.removeDocument(fileName);
},
},
);
}

const scriptSourceHelper = createScriptSourceHelper(info, { exclude: config.exclude });
const adapter = new GraphQLLanguageServiceAdapter(scriptSourceHelper, {
schema,
schemaErrors,
logger,
tag,
fragmentRegistry,
removeDuplicatedFragments,
});

const proxy = new LanguageServiceProxyBuilder(info)
.wrap('getCompletionsAtPosition', delegate => adapter.getCompletionAtPosition.bind(adapter, delegate))
.wrap('getSemanticDiagnostics', delegate => adapter.getSemanticDiagnostics.bind(adapter, delegate))
.wrap('getQuickInfoAtPosition', delegate => adapter.getQuickInfoAtPosition.bind(adapter, delegate))
.wrap('getDefinitionAndBoundSpan', delegate => adapter.getDefinitionAndBoundSpan.bind(adapter, delegate))
.wrap('getDefinitionAtPosition', delegate => adapter.getDefinitionAtPosition.bind(adapter, delegate))
.build();

schemaManager.registerOnChange(adapter.updateSchema.bind(adapter));
schemaManager.start();

return proxy;
}
90 changes: 5 additions & 85 deletions src/language-service-plugin/plugin-module-factory.ts
Original file line number Diff line number Diff line change
@@ -1,89 +1,9 @@
import ts from 'typescript/lib/tsserverlibrary';
import type { TsGraphQLPluginConfigOptions } from '../types';
import { GraphQLLanguageServiceAdapter } from '../graphql-language-service-adapter';
import { SchemaManagerFactory, createSchemaManagerHostFromLSPluginInfo } from '../schema-manager';
import { FragmentRegistry } from '../gql-ast-util';
import {
createScriptSourceHelper,
registerDocumentChangeEvent,
getTemplateNodeUnder,
findAllNodes,
getSanitizedTemplateText,
createFileNameFilter,
parseTagConfig,
} from '../ts-ast-util';
import { LanguageServiceProxyBuilder } from './language-service-proxy-builder';
import type ts from 'typescript/lib/tsserverlibrary';

function create(info: ts.server.PluginCreateInfo): ts.LanguageService {
const logger = (msg: string) => info.project.projectService.logger.info(`[ts-graphql-plugin] ${msg}`);
logger('config: ' + JSON.stringify(info.config));
const schemaManager = new SchemaManagerFactory(createSchemaManagerHostFromLSPluginInfo(info)).create();
const { schema, errors: schemaErrors } = schemaManager.getSchema();
const config = info.config as TsGraphQLPluginConfigOptions;
const tag = parseTagConfig(config.tag);
const removeDuplicatedFragments = config.removeDuplicatedFragments === false ? false : true;
const enabledGlobalFragments = config.enabledGlobalFragments !== false;
const isExcluded = createFileNameFilter({ specs: config.exclude, projectName: info.project.getProjectName() });
import { setModule } from './ts-server-module';

const fragmentRegistry = new FragmentRegistry({ logger });
if (enabledGlobalFragments) {
const handleAcquireOrUpdate = (fileName: string, sourceFile: ts.SourceFile, version: string) => {
fragmentRegistry.registerDocuments(
fileName,
version,
findAllNodes(sourceFile, node => getTemplateNodeUnder(node, tag)).map(node =>
getSanitizedTemplateText(node, sourceFile),
),
);
};

registerDocumentChangeEvent(
// Note:
// documentRegistry in ts.server.Project is annotated @internal
(info.project as any).documentRegistry as ts.DocumentRegistry,
{
onAcquire: (fileName, sourceFile, version) => {
if (!isExcluded(fileName) && info.languageServiceHost.getScriptFileNames().includes(fileName)) {
handleAcquireOrUpdate(fileName, sourceFile, version);
}
},
onUpdate: (fileName, sourceFile, version) => {
if (!isExcluded(fileName) && info.languageServiceHost.getScriptFileNames().includes(fileName)) {
if (fragmentRegistry.getFileCurrentVersion(fileName) === version) return;
handleAcquireOrUpdate(fileName, sourceFile, version);
}
},
onRelease: fileName => {
fragmentRegistry.removeDocument(fileName);
},
},
);
}

const scriptSourceHelper = createScriptSourceHelper(info, { exclude: config.exclude });
const adapter = new GraphQLLanguageServiceAdapter(scriptSourceHelper, {
schema,
schemaErrors,
logger,
tag,
fragmentRegistry,
removeDuplicatedFragments,
});

const proxy = new LanguageServiceProxyBuilder(info)
.wrap('getCompletionsAtPosition', delegate => adapter.getCompletionAtPosition.bind(adapter, delegate))
.wrap('getSemanticDiagnostics', delegate => adapter.getSemanticDiagnostics.bind(adapter, delegate))
.wrap('getQuickInfoAtPosition', delegate => adapter.getQuickInfoAtPosition.bind(adapter, delegate))
.wrap('getDefinitionAndBoundSpan', delegate => adapter.getDefinitionAndBoundSpan.bind(adapter, delegate))
.wrap('getDefinitionAtPosition', delegate => adapter.getDefinitionAtPosition.bind(adapter, delegate))
.build();

schemaManager.registerOnChange(adapter.updateSchema.bind(adapter));
schemaManager.start();

return proxy;
}

export const pluginModuleFactory: ts.server.PluginModuleFactory = () => {
export const pluginModuleFactory: ts.server.PluginModuleFactory = ({ typescript }) => {
setModule(typescript);
const { create } = require('./create-plugin') as typeof import('./create-plugin');
return { create };
};
11 changes: 11 additions & 0 deletions src/language-service-plugin/ts-server-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type ts from 'typescript/lib/tsserverlibrary';

let _module: typeof ts;

export function setModule(typescript: typeof ts) {
_module = typescript;
}

export function getModule() {
return _module;
}
3 changes: 2 additions & 1 deletion src/schema-manager/file-schema-manager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import path from 'node:path';
import type ts from 'typescript';
import { buildSchema, buildClientSchema } from 'graphql';

import type ts from '../tsmodule';

import { SchemaManager } from './schema-manager';
import type { SchemaManagerHost } from './types';

Expand Down
2 changes: 1 addition & 1 deletion src/schema-manager/schema-manager-host.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path from 'node:path';
import ts from 'typescript';
import ts from '../tsmodule';
import type { SchemaManagerHost, SchemaConfig } from './types';
import type { TsGraphQLPluginConfigOptions } from '../types';

Expand Down
2 changes: 1 addition & 1 deletion src/transformer/transformer-host.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type ts from 'typescript';
import type ts from '../tsmodule';
import { Kind, type DocumentNode, type FragmentDefinitionNode } from 'graphql';
import { getFragmentDependenciesForAST } from 'graphql-language-service';
import { AnalyzerFactory, type Analyzer, type ExtractFileResult } from '../analyzer';
Expand Down
2 changes: 1 addition & 1 deletion src/transformer/transformer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ts from 'typescript';
import { print, type DocumentNode } from 'graphql';
import ts from '../tsmodule';
import { astf, getTemplateNodeUnder, removeAliasFromImportDeclaration, type StrictTagCondition } from '../ts-ast-util';

export type DocumentTransformer = (documentNode: DocumentNode) => DocumentNode;
Expand Down
2 changes: 1 addition & 1 deletion src/ts-ast-util/ast-factory-alias.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import ts from 'typescript';
import ts from '../tsmodule';

export const astf = ts.factory;
3 changes: 2 additions & 1 deletion src/ts-ast-util/output-source.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import path from 'node:path';
import ts from 'typescript';

import ts from '../tsmodule';

import type { OutputSource } from './types';
import { astf } from './ast-factory-alias';
Expand Down
2 changes: 1 addition & 1 deletion src/ts-ast-util/register-document-change-event.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type ts from 'typescript';
import type ts from '../tsmodule';

type DocumentChangeEventListener = {
onAcquire: (fileName: string, sourceFile: ts.SourceFile, version: string) => void;
Expand Down
2 changes: 1 addition & 1 deletion src/ts-ast-util/script-host.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ts from 'typescript';
import ts from '../tsmodule';

export class ScriptHost implements ts.LanguageServiceHost {
private readonly _fileMap = new Map<string, string | undefined>();
Expand Down
2 changes: 1 addition & 1 deletion src/ts-ast-util/script-source-helper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ts from 'typescript';
import ts from '../tsmodule';
import { findAllNodes, findNode } from './utilily-functions';
import { TemplateExpressionResolver } from './template-expression-resolver';
import { createFileNameFilter } from './file-name-filter';
Expand Down
2 changes: 1 addition & 1 deletion src/ts-ast-util/tag-utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ts from 'typescript';
import ts from '../tsmodule';
import type { TagConfig, StrictTagCondition } from './types';

export const DEFAULT_TAG_CONDITION = {
Expand Down
2 changes: 1 addition & 1 deletion src/ts-ast-util/template-expression-resolver.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ts from 'typescript';
import ts from '../tsmodule';
import { location2pos, pos2location } from '../string-util';
import { findNode } from './utilily-functions';
import type { ComputePosition, ResolvedTemplateInfo, ResolveResult, ResolveErrorInfo } from './types';
Expand Down
2 changes: 1 addition & 1 deletion src/ts-ast-util/utilily-functions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ts from 'typescript';
import ts from '../tsmodule';
import { astf } from './ast-factory-alias';

function mergeNamedBinding(base: ts.NamedImportBindings | undefined, head: ts.NamedImportBindings | undefined) {
Expand Down
3 changes: 3 additions & 0 deletions src/tsmodule.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import * as ts from 'typescript';
export = ts;
3 changes: 3 additions & 0 deletions src/tsmodule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const { getModule } = require('./language-service-plugin/ts-server-module');

module.exports = getModule() ?? require('typescript');

0 comments on commit 39c3bf5

Please sign in to comment.