diff --git a/package.json b/package.json index 4583a97..b4234c8 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,18 @@ "private": true, "dependencies": { "babel-runtime": "^6.23.0", + "codemirror": "^5.56.0", + "codemirror-graphql": "^0.12.0", + "graphql": "^15.3.0", + "graphql-language-service": "^3.0.0", "immutable": "^4.0.0-rc.12", "is-hotkey": "^0.1.1", "konva": "^7.0.2", "lscache": "^1.1.0", + "markdown-it": "^11.0.0", "ramda": "^0.27.0", "react": "^16.1.1", + "react-codemirror2": "^7.2.1", "react-dom": "^16.0.0", "react-konva": "^16.13.0-3", "react-motion": "^0.5.2", diff --git a/src/createStore.js b/src/createStore.js index e3cb6ff..0b9c3bc 100644 --- a/src/createStore.js +++ b/src/createStore.js @@ -3,15 +3,17 @@ import { composeWithDevTools } from 'redux-devtools-extension' import editorModule from './modules/editor/store' import sidebarModule from './modules/sidebar/store' +import sidebarCodeEditorModule from './modules/sidebar/modules/codemirror/store' // DevTools const devToolsModule = { - enhancer: composeWithDevTools() + enhancer: composeWithDevTools(), } const modules = [ editorModule, sidebarModule, + sidebarCodeEditorModule, devToolsModule, ] diff --git a/src/modules/editor/__mock__/initialState.js b/src/modules/editor/__mock__/initialState.js index 1a38ad0..be0c36b 100644 --- a/src/modules/editor/__mock__/initialState.js +++ b/src/modules/editor/__mock__/initialState.js @@ -4,129 +4,123 @@ const initialStateComplex = { name: 'Vaccine', pos: { x: 1027, - y: 313 + y: 313, }, type: 'model', fields: [], - selected: false + selected: false, }, { name: 'ShotOrder', pos: { x: 426, - y: 605 + y: 605, }, type: 'model', fields: [ { name: 'applicationPlaces', type: '[ApplicationPlace]' }, - { name: 'company', type: '[Company]' } + { name: 'company', type: '[Company]' }, ], - selected: false + selected: false, }, { name: 'Campaign', pos: { x: 844, - y: 50 + y: 50, }, type: 'model', - fields: [ - { name: 'vaccines', type: '[Vaccine]' } - ], - selected: false + fields: [{ name: 'vaccines', type: '[Vaccine]' }], + selected: false, }, { name: 'unity', pos: { x: 323.5, - y: 332 + y: 332, }, type: 'relation', selected: false, - cardinality: 'hasOne' + cardinality: 'hasOne', }, { name: 'company', pos: { x: 240, - y: 619 + y: 619, }, type: 'relation', selected: false, - cardinality: 'hasOne' + cardinality: 'hasOne', }, { name: 'Company', pos: { x: 147, - y: 453 + y: 453, }, type: 'model', - fields: [ - { name: 'unity', type: 'Unity' } - ], - selected: false + fields: [{ name: 'unity', type: 'Unity' }], + selected: false, }, { name: 'Unity', pos: { x: 164, - y: 243 + y: 243, }, type: 'model', fields: [], - selected: false + selected: false, }, { name: 'ApplicationPlace', pos: { x: 505, - y: 204 + y: 204, }, type: 'model', fields: [ { name: 'unity', type: 'Unity' }, - { name: 'shotPackages', type: '[ShotPackage]' } + { name: 'shotPackages', type: '[ShotPackage]' }, ], - selected: false + selected: false, }, { name: 'cdaLot', pos: { x: 841.5, - y: 633 + y: 633, }, type: 'relation', selected: false, - cardinality: 'hasOne' + cardinality: 'hasOne', }, { name: 'CdaLot', pos: { x: 989, - y: 531 + y: 531, }, type: 'model', - fields: [ - { name: 'vaccine', type: 'Vaccine' } - ], - selected: false + fields: [{ name: 'vaccine', type: 'Vaccine' }], + selected: false, }, { name: 'vaccine', pos: { x: 872, - y: 401 + y: 401, }, type: 'relation', selected: false, - cardinality: 'hasOne' + cardinality: 'hasOne', }, { name: 'Person', pos: { x: 701, - y: 489 + y: 489, }, type: 'model', fields: [ @@ -135,361 +129,191 @@ const initialStateComplex = { { name: 'shotOrder', type: 'ShotOrder' }, { name: 'cdaLot', type: 'CdaLot' }, ], - selected: false + selected: false, }, { name: 'shotOrder', pos: { x: 603.5, - y: 641.5 + y: 641.5, }, type: 'relation', selected: false, - cardinality: 'hasOne' + cardinality: 'hasOne', }, { name: 'applicationPlace', pos: { x: 568.5, - y: 387.5 + y: 387.5, }, type: 'relation', - selected: false + selected: false, }, { name: 'ShotPackage', pos: { x: 691, - y: 268 + y: 268, }, type: 'model', - fields: [ - { name: 'vaccine', type: 'Vaccine' } - ], - selected: false + fields: [{ name: 'vaccine', type: 'Vaccine' }], + selected: false, }, { name: 'shotPackages', pos: { x: 649.5, - y: 86.5 + y: 86.5, }, type: 'relation', selected: false, - cardinality: 'hasMany' + cardinality: 'hasMany', }, { name: 'vaccines', pos: { x: 881.5, - y: 232 + y: 232, }, type: 'relation', selected: false, - cardinality: 'hasMany' + cardinality: 'hasMany', }, { name: 'applicationPlaces', pos: { x: 430.5, - y: 444 + y: 444, }, type: 'relation', selected: false, - cardinality: 'hasMany' - } + cardinality: 'hasMany', + }, ], edges: [ { type: 'hasMany', - nodes: [ - 'Campaign', - 'vaccines' - ], - points: [ - 905, - 111, - 926.5, - 277 - ] + nodes: ['Campaign', 'vaccines'], + points: [905, 111, 926.5, 277], }, { type: 'hasMany', - nodes: [ - 'vaccines', - 'Vaccine' - ], - points: [ - 926.5, - 277, - 1088, - 374 - ] + nodes: ['vaccines', 'Vaccine'], + points: [926.5, 277, 1088, 374], }, { type: 'hasOne', - nodes: [ - 'Company', - 'unity' - ], - points: [ - 208, - 514, - 368.5, - 377 - ] + nodes: ['Company', 'unity'], + points: [208, 514, 368.5, 377], }, { type: 'hasOne', - nodes: [ - 'unity', - 'Unity' - ], - points: [ - 368.5, - 377, - 225, - 304 - ] + nodes: ['unity', 'Unity'], + points: [368.5, 377, 225, 304], }, { type: 'hasOne', - nodes: [ - 'ApplicationPlace', - 'unity' - ], - points: [ - 566, - 265, - 368.5, - 377 - ] + nodes: ['ApplicationPlace', 'unity'], + points: [566, 265, 368.5, 377], }, { type: 'hasOne', - nodes: [ - 'ShotOrder', - 'company' - ], - points: [ - 487, - 666, - 285, - 664 - ] + nodes: ['ShotOrder', 'company'], + points: [487, 666, 285, 664], }, { type: 'hasOne', - nodes: [ - 'company', - 'Company' - ], - points: [ - 285, - 664, - 208, - 514 - ] + nodes: ['company', 'Company'], + points: [285, 664, 208, 514], }, { type: 'hasOne', - nodes: [ - 'Person', - 'vaccine' - ], - points: [ - 762, - 550, - 917, - 446 - ] + nodes: ['Person', 'vaccine'], + points: [762, 550, 917, 446], }, { type: 'hasOne', - nodes: [ - 'vaccine', - 'Vaccine' - ], - points: [ - 917, - 446, - 1088, - 374 - ] + nodes: ['vaccine', 'Vaccine'], + points: [917, 446, 1088, 374], }, { type: 'hasOne', - nodes: [ - 'Person', - 'applicationPlace' - ], - points: [ - 762, - 550, - 613.5, - 432.5 - ] + nodes: ['Person', 'applicationPlace'], + points: [762, 550, 613.5, 432.5], }, { type: 'hasOne', - nodes: [ - 'applicationPlace', - 'ApplicationPlace' - ], - points: [ - 613.5, - 432.5, - 566, - 265 - ] + nodes: ['applicationPlace', 'ApplicationPlace'], + points: [613.5, 432.5, 566, 265], }, { type: 'hasOne', - nodes: [ - 'Person', - 'cdaLot' - ], - points: [ - 762, - 550, - 886.5, - 678 - ] + nodes: ['Person', 'cdaLot'], + points: [762, 550, 886.5, 678], }, { type: 'hasOne', - nodes: [ - 'cdaLot', - 'CdaLot' - ], - points: [ - 886.5, - 678, - 1050, - 592 - ] + nodes: ['cdaLot', 'CdaLot'], + points: [886.5, 678, 1050, 592], }, { type: 'hasOne', - nodes: [ - 'CdaLot', - 'vaccine' - ], - points: [ - 1050, - 592, - 917, - 446 - ] + nodes: ['CdaLot', 'vaccine'], + points: [1050, 592, 917, 446], }, { type: 'hasMany', - nodes: [ - 'ShotOrder', - 'applicationPlaces' - ], - points: [ - 487, - 666, - 475.5, - 489 - ] + nodes: ['ShotOrder', 'applicationPlaces'], + points: [487, 666, 475.5, 489], }, { type: 'hasMany', - nodes: [ - 'applicationPlaces', - 'ApplicationPlace' - ], - points: [ - 475.5, - 489, - 566, - 265 - ] + nodes: ['applicationPlaces', 'ApplicationPlace'], + points: [475.5, 489, 566, 265], }, { type: 'hasMany', - nodes: [ - 'ApplicationPlace', - 'shotPackages' - ], - points: [ - 566, - 265, - 694.5, - 131.5 - ] + nodes: ['ApplicationPlace', 'shotPackages'], + points: [566, 265, 694.5, 131.5], }, { type: 'hasMany', - nodes: [ - 'shotPackages', - 'ShotPackage' - ], - points: [ - 694.5, - 131.5, - 752, - 329 - ] + nodes: ['shotPackages', 'ShotPackage'], + points: [694.5, 131.5, 752, 329], }, { type: 'hasOne', - nodes: [ - 'ShotPackage', - 'vaccine' - ], - points: [ - 752, - 329, - 917, - 446 - ] + nodes: ['ShotPackage', 'vaccine'], + points: [752, 329, 917, 446], }, { type: 'hasOne', - nodes: [ - 'Person', - 'shotOrder' - ], - points: [ - 762, - 550, - 648.5, - 686.5 - ] + nodes: ['Person', 'shotOrder'], + points: [762, 550, 648.5, 686.5], }, { type: 'hasOne', - nodes: [ - 'shotOrder', - 'ShotOrder' - ], - points: [ - 648.5, - 686.5, - 487, - 666 - ] - } + nodes: ['shotOrder', 'ShotOrder'], + points: [648.5, 686.5, 487, 666], + }, ], stage: { pos: { x: 0, - y: 0 - } + y: 0, + }, }, connector: { isConnecting: false, - connectedTo: null - } + connectedTo: null, + }, } const initialStateSimple = { nodes: [], edges: [], - stage: { pos: { x: -1, y: 0 } } + stage: { pos: { x: -1, y: 0 } }, } -export default initialStateComplex; +export default initialStateSimple diff --git a/src/modules/editor/__tests__/store.js b/src/modules/editor/__tests__/store.js index 565ba4d..738c5eb 100644 --- a/src/modules/editor/__tests__/store.js +++ b/src/modules/editor/__tests__/store.js @@ -10,4 +10,4 @@ describe('[Editor] Store', () => { const result = getModelFromRelation({ edges: edgesMock, nodeB: 'modelB' }) expect(result).toBe('ModelB') }) -}) \ No newline at end of file +}) diff --git a/src/modules/sidebar/containers/CodeContainer.js b/src/modules/sidebar/containers/CodeContainer.js index 85cd33d..32cc71d 100644 --- a/src/modules/sidebar/containers/CodeContainer.js +++ b/src/modules/sidebar/containers/CodeContainer.js @@ -5,54 +5,23 @@ import { compose } from 'recompose' import { connect } from 'react-redux' import { sort, ascend, prop } from 'ramda' -const Code = styled(Card)` - padding: 1em 1em 20em; - cursor: text; -` - -const FieldNode = styled.span` - line-height: 1.8em; - margin: 0; - font-size: 1em; - font-family: Fira Code, "Consolas", "Inconsolata", "Droid Sans Mono", "Monaco", monospace; - color: #ddd; - font-weight: 100; -` +import CodeEditor from '../modules/codemirror/containers/CodeEditor' -const FieldName = styled.span` - color: #ec63c5; - font-weight: 500; -` - -const TypeName = styled.span` - font-style: italic; +const Code = styled(Card)` + /* padding: 1em 1em 20em; */ + /* cursor: text; */ ` const CodeContainer = ({ nodes }) => ( - { nodes.map((node, key) => ( - - - { 'type ' } - { node.name } - { ' {' } - - { node.fields.map(({ name, type }, key) => ( -   { name + ':' } { type } - )) } - { '}' } -
-
- )) } +
) const mapStateToProps = ({ nodes, sidebar }) => { const modelNodes = nodes.filter(({ type }) => type === 'model') const sortedNodes = sort(ascend(prop('name')), modelNodes) - return { nodes: sortedNodes, sidebar }; + return { nodes: sortedNodes, sidebar } } -export default compose( - connect(mapStateToProps) -)(CodeContainer) \ No newline at end of file +export default compose(connect(mapStateToProps))(CodeContainer) diff --git a/src/modules/sidebar/modules/codemirror/containers/CodeEditor.js b/src/modules/sidebar/modules/codemirror/containers/CodeEditor.js new file mode 100644 index 0000000..885861b --- /dev/null +++ b/src/modules/sidebar/modules/codemirror/containers/CodeEditor.js @@ -0,0 +1,139 @@ +import React, { useCallback, useState, useEffect, useMemo } from 'react' +import { useSelector, useDispatch } from 'react-redux' +import { UnControlled as CodeMirror } from 'react-codemirror2' +import { buildSchema } from 'graphql' + +import onHasCompletion from '../lib/onHasCompletion' +import { updateCodeEditorValue } from '../store' + +import 'codemirror/lib/codemirror.css' +import 'codemirror/addon/hint/show-hint.css' +import 'codemirror/theme/dracula.css' + +require('codemirror/addon/hint/show-hint') +require('codemirror/addon/comment/comment') +require('codemirror/addon/edit/matchbrackets') +require('codemirror/addon/edit/closebrackets') +require('codemirror/addon/fold/foldgutter') +require('codemirror/addon/fold/brace-fold') +require('codemirror/addon/search/search') +require('codemirror/addon/search/searchcursor') +require('codemirror/addon/search/jump-to-line') +require('codemirror/addon/dialog/dialog') +require('codemirror/addon/lint/lint') +require('codemirror/keymap/sublime') +// require('codemirror-graphql/hint') +// require('codemirror-graphql/lint') +// require('codemirror-graphql/info') +// require('codemirror-graphql/jump') +require('../lib/hint') +require('codemirror-graphql/mode') + +const AUTO_COMPLETE_AFTER_KEY = /^[a-zA-Z0-9_@(]$/ + +const onKeyUp = (editor, event) => { + if (AUTO_COMPLETE_AFTER_KEY.test(event.key) && editor) { + editor.execCommand('autocomplete') + // const service = new LanguageService({ rawSchema: myGraphQLSchema }) + // debugger + } +} + +// @TODO Unsetup removing listeners +const setupEditor = (cmRef) => (editor) => { + cmRef.current = editor + editor.on('hasCompletion', onHasCompletion) +} + +function CodeEditor() { + let cmRef = React.useRef(null) + + const savedSchemaDsl = useSelector((state) => state.sidebarCodeEditor.value) + const dispatch = useDispatch() + const setSavedSchemaDsl = useCallback( + (value) => dispatch(updateCodeEditorValue(value)), + [dispatch] + ) + + const compiledSchema = useMemo(() => { + try { + return buildSchema(savedSchemaDsl) + } catch (error) {} + }, [savedSchemaDsl]) + + const [editorValue, setEditorValue] = useState(savedSchemaDsl) + + const handleBeforeChange = useCallback( + (editor, data, value) => { + try { + // Test if schema is buildable. + buildSchema(value) + + // Save the valid DSL schema. + setSavedSchemaDsl(value) + + // @TODO would it be good to give feedback on why is the schema wrong + // try { + // getDiagnostics(value, buildedSchema) + // } catch (error) { + // console.error(error) + // } + + console.log('-> SCHEMA SAVED!') + } catch (error) { + console.error(error) + } + + setEditorValue(value) + }, + [setSavedSchemaDsl] + ) + + useEffect(() => { + cmRef.current.options.hintOptions.schema = compiledSchema + cmRef.current.options.lint.schema = compiledSchema + // codemirror.signal(cmRef.current, 'change', cmRef.current) + }, [compiledSchema, cmRef]) + + useEffect(() => { + cmRef.current.setCursor(0) + }, [cmRef]) + + return ( + editor.showHint({ completeSingle: true }), + }, + gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], + }} + /> + ) +} + +export default CodeEditor diff --git a/src/modules/sidebar/modules/codemirror/lib/__tests__/serializer.js b/src/modules/sidebar/modules/codemirror/lib/__tests__/serializer.js new file mode 100644 index 0000000..12f9b6f --- /dev/null +++ b/src/modules/sidebar/modules/codemirror/lib/__tests__/serializer.js @@ -0,0 +1,265 @@ +import { buildSchema } from 'graphql' +import { + serializeSchemaToEditor, + mergeSerializedToEditorState, +} from '../serializer' + +const BASIC_SCHEMA = ` +type Post { + id: ID! + comments: [Comment!]! +} + +type Comment { + id: ID! + post: Post! +} + +type RootQuery { + post(id: ID!): Post +} + +schema { + query: RootQuery +} +` + +const compiledSchema = buildSchema(BASIC_SCHEMA) + +describe('Editor Serializer', () => { + it('should extract nodes of type model from compiled schema', () => { + const nodes = [ + { + fields: [ + { + name: 'id', + type: 'ID!', + }, + { + name: 'comments', + type: '[Comment!]!', + }, + ], + name: 'Post', + type: 'model', + }, + { + fields: [ + { + name: 'id', + type: 'ID!', + }, + { + name: 'post', + type: 'Post!', + }, + ], + name: 'Comment', + type: 'model', + }, + ] + + const result = serializeSchemaToEditor(compiledSchema) + + expect(result.nodes).toEqual(expect.arrayContaining(nodes)) + }) + + it('should extract nodes of type relation from compiled schema', () => { + const nodes = [ + { + fields: [ + { + name: 'id', + type: 'ID!', + }, + { + name: 'comments', + type: '[Comment!]!', + }, + ], + name: 'Post', + type: 'model', + }, + { + fields: [ + { + name: 'id', + type: 'ID!', + }, + { + name: 'post', + type: 'Post!', + }, + ], + name: 'Comment', + type: 'model', + }, + { + name: 'comments', + type: 'relation', + cardinality: 'hasMany', + }, + { + name: 'post', + type: 'relation', + cardinality: 'hasOne', + }, + ] + + const result = serializeSchemaToEditor(compiledSchema) + expect(result.nodes).toEqual(nodes) + }) + + it('should extract edges for nodes from compiled schema', () => { + const edges = [ + { + type: 'hasOne', + nodes: ['Comment', 'post'], + }, + { + type: 'hasMany', + nodes: ['Post', 'comments'], + }, + { + type: 'hasOne', + nodes: ['post', 'Post'], + }, + { + type: 'hasMany', + nodes: ['comments', 'Comment'], + }, + ] + + const result = serializeSchemaToEditor(compiledSchema) + expect(result.edges).toEqual(expect.arrayContaining(edges)) + }) + + it('should merge nodes positions from state with serialized schema', () => { + const nodesState = [ + { + fields: [ + { + name: 'comments', + type: '[Comment!]!', + }, + ], + name: 'Post', + type: 'model', + selected: false, + pos: { + x: 426, + y: 605, + }, + }, + { + fields: [ + { + name: 'id', + type: 'ID!', + }, + { + name: 'post', + type: 'Post!', + }, + ], + name: 'Comment', + type: 'model', + selected: false, + pos: { + x: 332, + y: 444, + }, + }, + { + cardinality: 'hasMany', + name: 'comments', + type: 'relation', + pos: { + x: 554, + y: 444, + }, + }, + { + cardinality: 'hasOne', + name: 'post', + type: 'relation', + pos: { + x: 339, + y: 222, + }, + }, + ] + + const expectedNodesState = [ + { + fields: [ + { + name: 'id', + type: 'ID!', + }, + { + name: 'comments', + type: '[Comment!]!', + }, + ], + name: 'Post', + type: 'model', + selected: false, + pos: { + x: 426, + y: 605, + }, + }, + { + fields: [ + { + name: 'id', + type: 'ID!', + }, + { + name: 'post', + type: 'Post!', + }, + ], + name: 'Comment', + type: 'model', + selected: false, + pos: { + x: 332, + y: 444, + }, + }, + { + cardinality: 'hasMany', + name: 'comments', + type: 'relation', + pos: { + x: 554, + y: 444, + }, + }, + { + cardinality: 'hasOne', + name: 'post', + type: 'relation', + pos: { + x: 339, + y: 222, + }, + }, + ] + + const serialized = serializeSchemaToEditor(compiledSchema) + const result = mergeSerializedToEditorState(serialized, { + nodes: nodesState, + }) + + // nodes: model + expect(result.nodes[0]).toEqual(expectedNodesState[0]) + expect(result.nodes[1]).toEqual(expectedNodesState[1]) + + // nodes: relation + expect(result.nodes[2]).toEqual(expectedNodesState[2]) + expect(result.nodes[3]).toEqual(expectedNodesState[3]) + }) +}) diff --git a/src/modules/sidebar/modules/codemirror/lib/helpers.js b/src/modules/sidebar/modules/codemirror/lib/helpers.js new file mode 100644 index 0000000..9a9ec5c --- /dev/null +++ b/src/modules/sidebar/modules/codemirror/lib/helpers.js @@ -0,0 +1,11 @@ +export function flatState(state, kinds = []) { + if (!state.kind) { + return kinds + } + + if (!state.prevState) { + return kinds.concat(state.kind) + } + + return flatState(state.prevState, kinds.concat(state.kind)) +} diff --git a/src/modules/sidebar/modules/codemirror/lib/hint.js b/src/modules/sidebar/modules/codemirror/lib/hint.js new file mode 100644 index 0000000..99cd5df --- /dev/null +++ b/src/modules/sidebar/modules/codemirror/lib/hint.js @@ -0,0 +1,257 @@ +import CodeMirror from 'codemirror' +import { + hintList, + objectValues, + getTypeInfo, + canUseDirective, +} from 'graphql-language-service-interface' +import { + isIntrospectionType, + isInputType, + isInputObjectType, + DirectiveLocation, +} from 'graphql' +import { RuleKinds } from 'graphql-language-service' +import { flatState } from './helpers' + +CodeMirror.registerHelper('hint', 'graphql', (editor, options) => { + const schema = options.schema + if (!schema) { + return + } + + const cur = editor.getCursor() + const token = editor.getTokenAt(cur) + + const rawResults = getSuggestions(token, schema) + + // console.log({ + // kind, + // names: getSuggestionsForFieldNames(token, typeInfo, schema), + // }) + + const tokenStart = + token.type !== null && /"|\w/.test(token.string[0]) + ? token.start + : token.end + const results = { + list: rawResults.map((item) => ({ + text: item.label, + type: item.type, + description: item.documentation, + isDeprecated: item.isDeprecated, + deprecationReason: item.deprecationReason, + })), + from: { line: cur.line, column: tokenStart }, + to: { line: cur.line, column: token.end }, + } + + if (results && results.list && results.list.length > 0) { + results.from = CodeMirror.Pos(results.from.line, results.from.column) + results.to = CodeMirror.Pos(results.to.line, results.to.column) + CodeMirror.signal(editor, 'hasCompletion', editor, results, token) + } + + return results +}) + +const isNotIntrospectionType = (f) => !isIntrospectionType(f) +const isNotInputType = (field) => !isInputObjectType(field) + +const isInputValueInArgs = (state) => { + const flatedState = flatState(state) + return ( + flatedState.includes(RuleKinds.ARGUMENTS_DEF) && + flatedState.includes(RuleKinds.INPUT_VALUE_DEF) + ) +} + +// Helper functions to get suggestions for each kinds +// @TODO Features: +// [X] Input arguments. +// [x] Directives arguments. +// [x] Directives locations. +// [x] Filter possible field types in Input types. +// [] Multiline comments. +// [] Machine fucking learning. +function getSuggestions(token, schema) { + // Get state + const state = + token.state.kind === 'Invalid' ? token.state.prevState : token.state + + // relieve flow errors by checking if `state` exists + if (!state || !schema) { + return [] + } + + const kind = state.kind + const step = state.step + const typeInfo = getTypeInfo(schema, token.state) + + console.log({ state, typeInfo, schema }) + + if (kind === RuleKinds.DOCUMENT) { + return hintList(token, [ + { label: 'type' }, + { label: 'schema' }, + { label: 'extend' }, + { label: 'enum' }, + { label: 'scalar' }, + { label: 'interface' }, + { label: 'union' }, + { label: 'input' }, + { label: 'directive' }, + ]) + } + + if (kind === RuleKinds.EXTEND_DEF) { + return hintList(token, [{ label: 'type' }]) + } + + const isDefiningInputValueInArgs = isInputValueInArgs(state) + + console.log(state.rule, 'RULES') + + // Defining the returning type of a field. + if ( + // foo: S + (kind === RuleKinds.NAMED_TYPE && + !isDefiningInputValueInArgs && + state.needsAdvance) || + // foo: + (kind === RuleKinds.FIELD_DEF && + !isDefiningInputValueInArgs && + !state.needsAdvance && + step === 3) || + // foo: [] + (kind === RuleKinds.LIST_TYPE && + !isDefiningInputValueInArgs && + step === 1 && + !state.needsAdvance) || + // step is used to ignore when is defining the field but not the returned type. + (kind === 'OperationTypeDef' && step > 1 && !state.needsAdvance) + ) { + const typeMap = schema.getTypeMap() + const graphqlTypes = objectValues(typeMap) + .filter(isNotIntrospectionType) + .filter(isNotInputType) + + return hintList( + token, + graphqlTypes.map((field) => ({ + label: field.name, + type: field.type, + documentation: field.description ?? undefined, + isDeprecated: field.isDeprecated, + deprecationReason: field.deprecationReason, + })) + ) + } + + if ( + isDefiningInputValueInArgs && + (!state.needsAdvance || kind === RuleKinds.NAMED_TYPE) + ) { + return getSuggestionsForInputValues(token, state, schema) + } + + // Directives. + if (kind === RuleKinds.DIRECTIVE) { + return getSuggestionsForDirective(token, state, schema) + } + + if ( + (kind === RuleKinds.DIRECTIVE_DEF && !state.needsAdvance && step === 5) || + (kind === 'DirectiveLocation' && state.needsAdvance) + ) { + return getSuggestionsForDirectiveLocations(token) + } + + if ( + kind === RuleKinds.ARGUMENTS && + state.prevState.kind === RuleKinds.DIRECTIVE + ) { + return getSuggestionsForDirectiveArgs(token, typeInfo) + } + + // console.log({ kind, step }) + // if (kind === RuleKinds.DIRECTIVE_DEF && step === 5) { + // console.log('LIST DIRECTIVES') + // } + + if (kind === RuleKinds.OBJECT_TYPE_DEF && state.needsAdvance === false) { + return hintList(token, [{ label: 'implements' }]) + } + + return [] +} + +function getSuggestionsForDirective(token, state, schema) { + if (!state.prevState && !state.prevState.kind) { + return [] + } + + const directives = schema + .getDirectives() + .filter((directive) => canUseDirective(state.prevState, directive)) + + return hintList( + token, + directives.map((directive) => ({ + label: directive.name, + documentation: directive.description || '', + })) + ) +} + +function getSuggestionsForInputValues(token, state, schema) { + if (!state.prevState && !state.prevState.kind) { + return [] + } + + const typeMap = schema.getTypeMap() + const graphqlTypes = objectValues(typeMap) + .filter(isInputType) + .filter(isNotIntrospectionType) + + return hintList( + token, + graphqlTypes.map((item) => ({ + label: item.name, + documentation: item.description || '', + })) + ) +} + +function getSuggestionsForDirectiveLocations(token) { + const locations = [ + DirectiveLocation.SCHEMA, + DirectiveLocation.SCALAR, + DirectiveLocation.OBJECT, + DirectiveLocation.FIELD_DEFINITION, + DirectiveLocation.INTERFACE, + DirectiveLocation.UNION, + DirectiveLocation.ENUM, + DirectiveLocation.ENUM_VALUE, + DirectiveLocation.INPUT_OBJECT, + DirectiveLocation.ARGUMENT_DEFINITION, + DirectiveLocation.INPUT_FIELD_DEFINITION, + ] + return hintList( + token, + locations.map((item) => ({ + label: item, + // documentation: item.description || '', + })) + ) +} + +function getSuggestionsForDirectiveArgs(token, typeInfo) { + return hintList( + token, + typeInfo.argDefs.map((item) => ({ + label: item.name, + documentation: item.description || '', + })) + ) +} diff --git a/src/modules/sidebar/modules/codemirror/lib/onHasCompletion.js b/src/modules/sidebar/modules/codemirror/lib/onHasCompletion.js new file mode 100644 index 0000000..b6ab301 --- /dev/null +++ b/src/modules/sidebar/modules/codemirror/lib/onHasCompletion.js @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2019 GraphQL Contributors. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +// import CM from 'codemirror'; + +import { + GraphQLNonNull, + GraphQLList, + // GraphQLType, + // GraphQLField, +} from 'graphql' +import MD from 'markdown-it' + +const md = new MD() + +/** + * Render a custom UI for CodeMirror's hint which includes additional info + * about the type and description for the selected context. + */ +export default function onHasCompletion(_cm, data, onHintInformationRender) { + const CodeMirror = require('codemirror') + + let information + let deprecation + + // When a hint result is selected, we augment the UI with information. + CodeMirror.on(data, 'select', (ctx, el) => { + // Only the first time (usually when the hint UI is first displayed) + // do we create the information nodes. + if (!information) { + const hintsUl = el.parentNode + + // This "information" node will contain the additional info about the + // highlighted typeahead option. + information = document.createElement('div') + information.className = 'CodeMirror-hint-information' + hintsUl.appendChild(information) + + // This "deprecation" node will contain info about deprecated usage. + deprecation = document.createElement('div') + deprecation.className = 'CodeMirror-hint-deprecation' + hintsUl.appendChild(deprecation) + + // When CodeMirror attempts to remove the hint UI, we detect that it was + // removed and in turn remove the information nodes. + let onRemoveFn + hintsUl.addEventListener( + 'DOMNodeRemoved', + (onRemoveFn = (event) => { + if (event.target === hintsUl) { + hintsUl.removeEventListener('DOMNodeRemoved', onRemoveFn) + information = null + deprecation = null + onRemoveFn = null + } + }) + ) + } + + // Now that the UI has been set up, add info to information. + const description = ctx.description + ? md.render(ctx.description) + : 'Self descriptive.' + const type = ctx.type + ? '' + renderType(ctx.type) + '' + : '' + + information.innerHTML = + '
' + + (description.slice(0, 3) === '

' + ? '

' + type + description.slice(3) + : type + description) + + '

' + + if (ctx && deprecation && ctx.isDeprecated) { + const reason = ctx.deprecationReason + ? md.render(ctx.deprecationReason) + : '' + deprecation.innerHTML = + 'Deprecated' + reason + deprecation.style.display = 'block' + } else if (deprecation) { + deprecation.style.display = 'none' + } + + // Additional rendering? + // if (onHintInformationRender) { + // onHintInformationRender(information) + // } + }) +} + +function renderType(type) { + if (type instanceof GraphQLNonNull) { + return `${renderType(type.ofType)}!` + } + if (type instanceof GraphQLList) { + return `[${renderType(type.ofType)}]` + } + return `${type.name}` +} diff --git a/src/modules/sidebar/modules/codemirror/lib/serializer.js b/src/modules/sidebar/modules/codemirror/lib/serializer.js new file mode 100644 index 0000000..58d3d72 --- /dev/null +++ b/src/modules/sidebar/modules/codemirror/lib/serializer.js @@ -0,0 +1,125 @@ +// @TODO: Consider moving this lib to the editor module. + +import { RuleKinds } from 'graphql-language-service' +import { isInputObjectType, isIntrospectionType, isObjectType } from 'graphql' +import { pipe, eqProps } from 'ramda' + +const isNotIntrospectionType = (f) => !isIntrospectionType(f) +const isNotInputType = (field) => !isInputObjectType(field) +const getFieldsFromType = (value) => Object.entries(value.getFields()) + +const getValueFromAstField = (ast) => { + if (ast?.name?.value && !ast.type) { + return ast.name.value + } + + if (!ast?.type) { + return false + } + + return getValueFromAstField(ast.type) +} + +const matchFromAst = (ast, predicate) => { + if (predicate(ast)) { + return true + } + + if (!ast.type) { + return false + } + + return matchFromAst(ast.type, predicate) +} + +export function serializeSchemaToEditor(schema) { + const typeMap = schema.getTypeMap() + // const graphqlTypes = objectValues(typeMap) + + const potentialNodes = Object.entries(typeMap).filter( + ([key, value]) => + isNotIntrospectionType(value) && + isNotInputType(value) && + isObjectType(value) && + key !== schema.getQueryType()?.name && + key !== schema.getMutationType()?.name && + key !== schema.getSubscriptionType()?.name + ) + + const modelNodes = potentialNodes.map(([key, value]) => { + const fields = getFieldsFromType(value).map(([name, field]) => { + return { name, type: field.type.toString() } + }) + + return { + name: key, + type: 'model', + fields, + } + }) + + const relationFields = potentialNodes + // flat and extract fields from potential nodes (model) and add the parent + // type owner of the field as a third item: [fieldName, fieldAst, parentType] + .reduce((prev, [key, value]) => { + const result = getFieldsFromType(value).map((pair) => pair.concat(key)) + return prev.concat(result) + }, []) + // only fields which relates to potential nodes. + .filter(([name, field]) => + matchFromAst(field.astNode, (ast) => + potentialNodes.some( + ([name]) => ast?.name?.value && name === ast?.name?.value + ) + ) + ) + + const relationNodes = relationFields.map(([name, field]) => { + const isHasMany = matchFromAst( + field.astNode, + (ast) => ast?.kind === RuleKinds.LIST_TYPE + ) + + return { + name, + type: 'relation', + cardinality: isHasMany ? 'hasMany' : 'hasOne', + } + }) + + const edges = relationFields + .map(([name, field, parentType]) => { + const type = matchFromAst( + field.astNode, + (ast) => ast?.kind === RuleKinds.LIST_TYPE + ) + ? 'hasMany' + : 'hasOne' + + const valueType = getValueFromAstField(field.astNode) + + return [ + { + type, + nodes: [name, valueType], + }, + { + type, + nodes: [parentType, name], + }, + ] + }) + .flat() + + return { nodes: modelNodes.concat(relationNodes), edges: edges } +} + +export function mergeSerializedToEditorState(serializedSchema, editorState) { + const currentNodes = editorState.nodes || [] + const nodes = serializedSchema.nodes.map((node) => { + const currentNode = currentNodes.find(eqProps('name', node)) + return { ...currentNode, ...node } + }) + + return { ...editorState, nodes } +} diff --git a/src/modules/sidebar/modules/codemirror/store.js b/src/modules/sidebar/modules/codemirror/store.js new file mode 100644 index 0000000..e7d3313 --- /dev/null +++ b/src/modules/sidebar/modules/codemirror/store.js @@ -0,0 +1,73 @@ +import { createAction } from 'redux-actions' +import { BOOT } from 'redux-boot' +import lscache from 'lscache' +import { buildSchema } from 'graphql' +import { + serializeSchemaToEditor, + mergeSerializedToEditorState, +} from './lib/serializer' + +export const updateCodeEditorValue = createAction( + 'sidebard/code-editor/UPDATE_VALUE' +) + +const INITIAL_SCHEMA = ` +type Product { + id: ID! + name: String! +} + +type User { + id: ID! + wishlist: [Product!]! +} + +type RootQuery { + product(id: ID!): Product +} + +schema { + query: RootQuery +}` + +const getInitialState = (state) => ({ + ...state, + sidebarCodeEditor: lscache.get('sidebarCodeEditor') || { + value: INITIAL_SCHEMA, + }, +}) + +const reducer = { + [BOOT]: getInitialState, + [updateCodeEditorValue]: (state, action) => { + const schema = buildSchema(action.payload) + const serializedSchema = serializeSchemaToEditor(schema) + const newEditorState = mergeSerializedToEditorState(serializedSchema, state) + return { + ...state, + ...newEditorState, + sidebarCodeEditor: { value: action.payload }, + } + }, +} + +// const middleware = { +// [updateCodeEditorValue]: store => next => action => { +// return next(action) +// } +// } + +const enhancer = (createStore) => (reducer, initialState, enhancer) => { + const store = createStore(reducer, initialState, enhancer) + + // Updates local storage. + store.subscribe(() => { + const state = store.getState() + + lscache.set('sidebarCodeEditor', state.sidebarCodeEditor) + }) + + return store +} + +export default { reducer, enhancer } diff --git a/src/modules/sidebar/store.js b/src/modules/sidebar/store.js index f0fc879..4a2d9f3 100644 --- a/src/modules/sidebar/store.js +++ b/src/modules/sidebar/store.js @@ -5,20 +5,18 @@ import { over, lensPath } from 'ramda' export const toggleSidebar = createAction('sidebar/TOGGLE') export const resetSidebar = createAction('sidebar/RESET') -const getInitialState = state => ({ +const getInitialState = (state) => ({ ...state, sidebar: { - isOpen: false - } + isOpen: true, + }, }) const reducer = { [BOOT]: (state, action) => getInitialState(state), [resetSidebar]: (state, action) => getInitialState(state), - [toggleSidebar]: (state, action) => over( - lensPath(['sidebar', 'isOpen']), - value => !value - )(state) + [toggleSidebar]: (state, action) => + over(lensPath(['sidebar', 'isOpen']), (value) => !value)(state), } -export default { reducer } \ No newline at end of file +export default { reducer } diff --git a/yarn.lock b/yarn.lock index 7b6a2bf..b2cbbd3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2932,6 +2932,19 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= +codemirror-graphql@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/codemirror-graphql/-/codemirror-graphql-0.12.0.tgz#55f782ffaaea7ff8dbda037e6b97b584dff2402a" + integrity sha512-C/0vKzQT5Uw+DLz6/MwKer29FQM52sh9Bem+VdDW42094j8nES1sdnuqj4k5ahNdQpW4FmVeoj/ngn2g3AWmgg== + dependencies: + graphql-language-service-interface "^2.4.0" + graphql-language-service-parser "^1.6.0" + +codemirror@^5.56.0: + version "5.56.0" + resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.56.0.tgz#675640fcc780105cd22d3faa738b5d7ea6426f61" + integrity sha512-MfKVmYgifXjQpLSgpETuih7A7WTTIsxvKfSLGseTY5+qt0E1UD1wblZGM6WLenORo8sgmf+3X+WTe2WF7mufyw== + collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -3928,7 +3941,7 @@ entities@^1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== -entities@^2.0.0: +entities@^2.0.0, entities@~2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== @@ -4939,6 +4952,46 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== +graphql-language-service-interface@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/graphql-language-service-interface/-/graphql-language-service-interface-2.4.0.tgz#4e2e63242c76197c4f56c61122db68187ea566b3" + integrity sha512-r7DQPyhCFY5TlpEukdh9tekJ9hAc7MD9TdOsb5CfAPlsIb1/faVVo2Ty19PxGSYDxygXjwpKLOQD0LqqFuw63A== + dependencies: + graphql-language-service-parser "^1.6.0" + graphql-language-service-types "^1.6.0" + graphql-language-service-utils "^2.4.0" + vscode-languageserver-types "^3.15.1" + +graphql-language-service-parser@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/graphql-language-service-parser/-/graphql-language-service-parser-1.6.0.tgz#53a97619461ed41ae237b9070e7e20cfaca56118" + integrity sha512-tkfYXl6WBECWNcsyw7O09c0oCMrxqr3JGyDNvdISDSDhvk8EwEk+AOweBEJkycJwoiv1lVlM+EdLfj7dzw4/6w== + +graphql-language-service-types@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/graphql-language-service-types/-/graphql-language-service-types-1.6.0.tgz#32529bb9d2e3b8ae9dd6d9646e15481ac5dfe9ad" + integrity sha512-7y4pkhTL+MtarC8CeHpvRYVc6pkT6ITdQXs+Gr7OClnk4Lz5nQEK0eO29kUdd0jEnYfHyh5iqhnL5Owfy+wT0A== + +graphql-language-service-utils@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/graphql-language-service-utils/-/graphql-language-service-utils-2.4.0.tgz#d4992a1968860abc43a118de498fec84361a4b87" + integrity sha512-aLa+8iqb7AJYgdcawsKH8/KLc14DHcRsnveshOm8hN6bBVT0YiQP6mEfJoci741O74uaDF2zj1J3c02vorichw== + dependencies: + graphql-language-service-types "^1.6.0" + +graphql-language-service@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/graphql-language-service/-/graphql-language-service-3.0.0.tgz#953c3bbf4cc35ff9a7bd54e428bca17832367490" + integrity sha512-ySPrbecxtjQtzIFdCIOoSJzaco9dQvZK+wZN/tTjcSEEWh3fgMYXmctAG+WddJiZg3AB7I4Xsb2AsO5yFBbLHg== + dependencies: + graphql-language-service-interface "^2.4.0" + graphql-language-service-types "^1.6.0" + +graphql@^15.3.0: + version "15.3.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278" + integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w== + growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" @@ -6560,6 +6613,13 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= +linkify-it@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.2.tgz#f55eeb8bc1d3ae754049e124ab3bb56d97797fb8" + integrity sha512-gDBO4aHNZS6coiZCKVhSNh43F9ioIL4JwRjLZPkoLIY4yZFwg264Y5lu2x6rb1Js42Gh6Yqm2f6L2AJcnkzinQ== + dependencies: + uc.micro "^1.0.1" + load-json-file@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" @@ -6751,6 +6811,17 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +markdown-it@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-11.0.0.tgz#dbfc30363e43d756ebc52c38586b91b90046b876" + integrity sha512-+CvOnmbSubmQFSA9dKz1BRiaSMV7rhexl3sngKqFyXSagoA3fBdJQ8oZWtRy2knXdpDXaBw44euz37DeJQ9asg== + dependencies: + argparse "^1.0.7" + entities "~2.0.0" + linkify-it "^3.0.1" + mdurl "^1.0.1" + uc.micro "^1.0.5" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -6770,6 +6841,11 @@ mdn-data@2.0.6: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978" integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA== +mdurl@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -8744,6 +8820,11 @@ react-app-polyfill@^1.0.6: regenerator-runtime "^0.13.3" whatwg-fetch "^3.0.0" +react-codemirror2@^7.2.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-7.2.1.tgz#38dab492fcbe5fb8ebf5630e5bb7922db8d3a10c" + integrity sha512-t7YFmz1AXdlImgHXA9Ja0T6AWuopilub24jRaQdPVbzUJVNKIYuy3uCFZYa7CE5S3UW6SrSa5nAqVQvtzRF9gw== + react-dev-utils@^10.2.1: version "10.2.1" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-10.2.1.tgz#f6de325ae25fa4d546d09df4bb1befdc6dd19c19" @@ -10638,6 +10719,11 @@ ua-parser-js@^0.7.18: resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777" integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ== +uc.micro@^1.0.1, uc.micro@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -10859,6 +10945,11 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== +vscode-languageserver-types@^3.15.1: + version "3.15.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de" + integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ== + w3c-hr-time@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"