From 608352d2da595cd744f17092665810e8dd4f2f50 Mon Sep 17 00:00:00 2001 From: Aleksandr Kanunnikov Date: Sat, 5 Feb 2022 02:41:00 +0300 Subject: [PATCH 01/31] draft --- package.json | 2 +- src/definition-providers/entry.ts | 6 +- src/definition-providers/glimmer-script.ts | 249 +++++++++++++++++++++ tsconfig.json | 2 +- yarn.lock | 50 ++--- 5 files changed, 281 insertions(+), 28 deletions(-) create mode 100644 src/definition-providers/glimmer-script.ts diff --git a/package.json b/package.json index 8da8ed21..d2dcd06e 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "main": "lib/index.js", "typings": "lib/index.d.ts", "dependencies": { - "@glimmer/syntax": "^0.73.1", + "@glimmer/syntax": "^0.83.1", "@lifeart/ember-extract-inline-templates": "2.1.0", "ast-types": "^0.14.2", "dag-map": "^2.0.2", diff --git a/src/definition-providers/entry.ts b/src/definition-providers/entry.ts index 07d55bb3..b0fce03e 100644 --- a/src/definition-providers/entry.ts +++ b/src/definition-providers/entry.ts @@ -3,14 +3,16 @@ import Server from './../server'; import { getExtension } from './../utils/file-extension'; import TemplateDefinitionProvider from './template'; import ScriptDefinitionProvider from './script'; - +import GlimmerScriptDefinitionProvider from './glimmer-script'; export default class DefinitionProvider { public template!: TemplateDefinitionProvider; public script!: ScriptDefinitionProvider; + public glimmerScript!: GlimmerScriptDefinitionProvider; constructor(private server: Server) { this.template = new TemplateDefinitionProvider(server); this.script = new ScriptDefinitionProvider(server); + this.glimmerScript = new GlimmerScriptDefinitionProvider(server); } async handle(params: TextDocumentPositionParams): Promise { @@ -29,6 +31,8 @@ export default class DefinitionProvider { return await this.template.handle(params, project); } else if (extension === '.js' || extension === '.ts') { return await this.script.handle(params, project); + } else if (extension === '.gts' || extension === '.gjs') { + return await this.glimmerScript.handle(params, project); } else { return null; } diff --git a/src/definition-providers/glimmer-script.ts b/src/definition-providers/glimmer-script.ts new file mode 100644 index 00000000..79a84791 --- /dev/null +++ b/src/definition-providers/glimmer-script.ts @@ -0,0 +1,249 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import Server from './../server'; +import ASTPath from './../glimmer-utils'; +import { TextDocumentPositionParams, Definition, Location } from 'vscode-languageserver/node'; +import { parseScriptFile as parse } from 'ember-meta-explorer'; +import { containsPosition, toPosition } from './../estree-utils'; +import { queryELSAddonsAPIChain } from './../utils/addon-api'; +import { Project } from '../project'; +import { getTemplateLocals, preprocess, ASTv1 } from '@glimmer/syntax'; +import { Range as LSRange } from 'vscode-languageserver/node'; + +class TemplateData { + loc: LSRange; + content: string; + constructor(loc: LSRange, content: string) { + this.loc = loc; + this.content = content; + } + get placeholder() { + return `__GLIMMER_TEMPLATE__`; + } + get locals() { + return getTemplateLocals(this.content); + } + get ast(): ASTv1.Template { + return preprocess(this.content); + } +} + +class FileRange { + start = 0; + columns = 0; + characters: string[] = []; + constructor(start = 0) { + this.start = start; + } + addColumn(character = '') { + this.columns++; + this.characters.push(character); + } + get line() { + return this.characters.join(''); + } +} + +export function getFileRanges(file = '') { + const ranges = [new FileRange()]; + + for (let i = 0; i < file.length; i++) { + if (file.charAt(i) !== '\n') { + ranges[ranges.length - 1].addColumn(file.charAt(i)); + } else { + ranges.push(new FileRange(i)); + } + } + + return ranges; +} + +const TEMPLATE_START = ''; +// const HTML_COMMENT_START = ''; +// const HBS_COMMENT_START = '{{!--'; +// const HBS_COMMENT_END = '--}}'; +// const HBS_COMMENT_INLINE_START = '{{! '; +// const HBS_COMMENT_INLINE_END = '}}'; + +const STATE = { + OPEN: 0, + HTML_COMMENT_OPEN: 2, + HTML_COMMENT_CLOSE: 3, + CLOSE: 1, +}; + +class TPosition { + line = 0; + character = 0; + constructor(line = 0, character = 0) { + this.line = line; + this.character = character; + } +} + +export class RangeWalker { + constructor(lines: FileRange[]) { + this.lines = lines; + } + lines: FileRange[] = []; + templates() { + const results: TemplateData[] = []; + let state = STATE.CLOSE; + + let buffer: string[] = []; + + const params = { + start: new TPosition(), + end: new TPosition(), + }; + + const openTemplate = (line: FileRange, offset: number) => { + params.start = new TPosition(this.lines.indexOf(line), offset); + }; + + const completeTemplate = (line: FileRange, offset: number) => { + params.end = new TPosition(this.lines.indexOf(line), offset); + results.push( + new TemplateData( + { + start: params.start, + end: params.end, + }, + buffer.join('') + ) + ); + buffer = []; + }; + + this.lines.forEach((fileRange) => { + let line = fileRange.line; + let offset = 0; + + while (line.length) { + if (state === STATE.CLOSE) { + const index = line.indexOf(TEMPLATE_START); + + if (index === -1) { + return; + } else { + offset = offset + index + TEMPLATE_START.length; + line = line.slice(index + TEMPLATE_START.length); + state = STATE.OPEN; + openTemplate(fileRange, offset); + } + } else if (state === STATE.OPEN) { + const index = line.indexOf(TEMPLATE_END); + + if (index === -1) { + buffer.push(line); + buffer.push('\n'); + + return; + } else { + buffer.push(line.slice(0, index)); + line = line.slice(index + TEMPLATE_END.length); + offset = offset + index; + state = STATE.CLOSE; + completeTemplate(fileRange, offset); + } + } else { + // OOPS + } + } + }); + + return results; + } +} + +export default class GlimmerScriptDefinitionProvider { + constructor(private server: Server) {} + async handle(params: TextDocumentPositionParams, project: Project): Promise { + const uri = params.textDocument.uri; + const { root } = project; + const document = this.server.documents.get(uri); + + if (!document) { + return null; + } + + const content = document.getText(); + + const ranges = getFileRanges(content); + + const rangeWalker = new RangeWalker(ranges); + + const templates = rangeWalker.templates(); + + // __GLIMMER_TEMPLATE/**/ + console.log(templates); + + let script = content; + + templates.forEach((t) => { + const start = ranges[t.loc.start.line].start + t.loc.start.character; + const end = ranges[t.loc.end.line].start + t.loc.end.character; + const newLines = new Array(t.loc.end.line - t.loc.start.line).fill('\n'); + const tail = new Array(t.loc.end.character).fill(' '); + const tokens = t.locals.join(','); + // keep original template size + + script = script.substring(0, start) + `${t.placeholder} = [${tokens}]${newLines}${tail}` + script.substring(end); + }); + + const templateForPosition = templates.find((el) => { + return containsPosition( + { + start: { + line: el.loc.start.line, + column: el.loc.start.character, + }, + end: { + line: el.loc.end.line, + column: el.loc.end.character, + }, + }, + toPosition(params.position) + ); + }); + + if (templateForPosition) { + // do logic to get more meta from js scope for template position + } else { + // looks like we could "fix" template and continue in script branch; + } + // @to-do - figure out how to patch babel ast with hbs + // or don't patch it, and just have 2 refs from hbs ast to scope of js ast + + const ast = parse(script, { + sourceType: 'module', + }); + + const astPath = ASTPath.toPosition(ast, toPosition(params.position), content); + + if (!astPath) { + return null; + } + + const results: Location[] = await queryELSAddonsAPIChain(project.builtinProviders.definitionProviders, root, { + focusPath: astPath, + type: 'glimmerScript', + textDocument: params.textDocument, + position: params.position, + results: [], + server: this.server, + }); + + const addonResults = await queryELSAddonsAPIChain(project.providers.definitionProviders, root, { + focusPath: astPath, + type: 'glimmerScript', + textDocument: params.textDocument, + position: params.position, + results, + server: this.server, + }); + + return addonResults; + } +} diff --git a/tsconfig.json b/tsconfig.json index ee268432..6e2640b9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ "allowSyntheticDefaultImports": true, "moduleResolution": "node", "sourceMap": true, - "lib" : [ "es2019", "WebWorker" ], + "lib" : [ "es2020", "WebWorker" ], "outDir": "./lib", "strictNullChecks": true, "noImplicitAny": true, diff --git a/yarn.lock b/yarn.lock index 0d8e9f74..6f317198 100644 --- a/yarn.lock +++ b/yarn.lock @@ -536,13 +536,6 @@ dependencies: "@simple-dom/interface" "^1.4.0" -"@glimmer/interfaces@0.73.1": - version "0.73.1" - resolved "https://registry.yarnpkg.com/@glimmer/interfaces/-/interfaces-0.73.1.tgz#ad1dffb635c19b66230c45894e15f3a34d90e5b0" - integrity sha512-MttMIyWGZ+IbiJJpr4q9dwMmn+7eOMdxjMiuaEYeLBN/DXe8T9VePqgapURkSL9CEmKByB/pLvcaVTSYfm0RYg== - dependencies: - "@simple-dom/interface" "^1.4.0" - "@glimmer/interfaces@0.82.0": version "0.82.0" resolved "https://registry.yarnpkg.com/@glimmer/interfaces/-/interfaces-0.82.0.tgz#e3f21e51a421b4c1ee34dcb682c719832f4a36f5" @@ -550,6 +543,13 @@ dependencies: "@simple-dom/interface" "^1.4.0" +"@glimmer/interfaces@0.83.1": + version "0.83.1" + resolved "https://registry.yarnpkg.com/@glimmer/interfaces/-/interfaces-0.83.1.tgz#fb16f5f683ddc55f130887b6141f58c0751350fe" + integrity sha512-rjAztghzX97v8I4rk3+NguM3XGYcFjc/GbJ8qrEj19KF2lUDoDBW1sB7f0tov3BD5HlrGXei/vOh4+DHfjeB5w== + dependencies: + "@simple-dom/interface" "^1.4.0" + "@glimmer/reference@^0.65.0": version "0.65.1" resolved "https://registry.yarnpkg.com/@glimmer/reference/-/reference-0.65.1.tgz#44515e7bef58c9ce4ea65c2eb3403caec11a2060" @@ -581,15 +581,15 @@ "@handlebars/parser" "^1.1.0" simple-html-tokenizer "^0.5.10" -"@glimmer/syntax@^0.73.1": - version "0.73.1" - resolved "https://registry.yarnpkg.com/@glimmer/syntax/-/syntax-0.73.1.tgz#9e3c898ba91ddb1d4e6f413452b0b84700910c83" - integrity sha512-TpTK9yAaoDOzdo+5itoJPRxrVWacYlGuHtJvATi96RJUTAxPz9b9M3AcVm0atcZW7WiA8X8J/weQ2KcIVgAYmw== +"@glimmer/syntax@^0.83.1": + version "0.83.1" + resolved "https://registry.yarnpkg.com/@glimmer/syntax/-/syntax-0.83.1.tgz#7e18dd445871c157ba0281f12a4fbf316fa49b41" + integrity sha512-n3vEd0GtjtgkOsd2gqkSimp8ecqq5KrHyana/s1XJZvVAPD5rMWT9WvAVWG8XAktns8BxjwLIUoj/vkOfA+eHg== dependencies: - "@glimmer/interfaces" "0.73.1" - "@glimmer/util" "0.73.1" - "@handlebars/parser" "^2.0.0" - simple-html-tokenizer "^0.5.10" + "@glimmer/interfaces" "0.83.1" + "@glimmer/util" "0.83.1" + "@handlebars/parser" "~2.0.0" + simple-html-tokenizer "^0.5.11" "@glimmer/util@0.65.1": version "0.65.1" @@ -600,15 +600,6 @@ "@glimmer/interfaces" "0.65.1" "@simple-dom/interface" "^1.4.0" -"@glimmer/util@0.73.1": - version "0.73.1" - resolved "https://registry.yarnpkg.com/@glimmer/util/-/util-0.73.1.tgz#f89bb7adba18315e00fbdb5a7c5a4913bbdebc6e" - integrity sha512-zFXY9OgR6wx7Cy4yYJkmgoUsdx3MQYAmZytuEDTghLXJyUoSh5Jcvner+qhFkqLSzj2pvE4btn3yUNnx3QOBtg== - dependencies: - "@glimmer/env" "0.1.7" - "@glimmer/interfaces" "0.73.1" - "@simple-dom/interface" "^1.4.0" - "@glimmer/util@0.82.0": version "0.82.0" resolved "https://registry.yarnpkg.com/@glimmer/util/-/util-0.82.0.tgz#d6ae4985f70343898d4b2297a4b3b1f5feda1934" @@ -618,6 +609,15 @@ "@glimmer/interfaces" "0.82.0" "@simple-dom/interface" "^1.4.0" +"@glimmer/util@0.83.1": + version "0.83.1" + resolved "https://registry.yarnpkg.com/@glimmer/util/-/util-0.83.1.tgz#cc7511b03164d658cf6e3262fce5a0fcb82edceb" + integrity sha512-amvjtl9dvrkxsoitXAly9W5NUaLIE3A2J2tWhBWIL1Z6DOFotfX7ytIosOIcPhJLZCtiXPHzMutQRv0G/MSMsA== + dependencies: + "@glimmer/env" "0.1.7" + "@glimmer/interfaces" "0.83.1" + "@simple-dom/interface" "^1.4.0" + "@glimmer/validator@0.65.1", "@glimmer/validator@^0.65.0": version "0.65.1" resolved "https://registry.yarnpkg.com/@glimmer/validator/-/validator-0.65.1.tgz#07ebd13952660d341fea8e5b606b512fe1dbc176" @@ -631,7 +631,7 @@ resolved "https://registry.yarnpkg.com/@handlebars/parser/-/parser-1.1.0.tgz#d6dbc7574774b238114582410e8fee0dc3532bdf" integrity sha512-rR7tJoSwJ2eooOpYGxGGW95sLq6GXUaS1UtWvN7pei6n2/okYvCGld9vsUTvkl2migxbkszsycwtMf/GEc1k1A== -"@handlebars/parser@^2.0.0", "@handlebars/parser@~2.0.0": +"@handlebars/parser@~2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@handlebars/parser/-/parser-2.0.0.tgz#5e8b7298f31ff8f7b260e6b7363c7e9ceed7d9c5" integrity sha512-EP9uEDZv/L5Qh9IWuMUGJRfwhXJ4h1dqKTT4/3+tY0eu7sPis7xh23j61SYUnNF4vqCQvvUXpDo9Bh/+q1zASA== From a8211f96649c8e89e8716da54aeb565e5206d6b0 Mon Sep 17 00:00:00 2001 From: Aleksandr Kanunnikov Date: Sat, 5 Feb 2022 12:22:32 +0300 Subject: [PATCH 02/31] file renages test --- src/definition-providers/glimmer-script.ts | 151 +----------------- src/utils/glimmer-script.ts | 177 +++++++++++++++++++++ test/utils/glimmer-script-test.ts | 41 +++++ 3 files changed, 219 insertions(+), 150 deletions(-) create mode 100644 src/utils/glimmer-script.ts create mode 100644 test/utils/glimmer-script-test.ts diff --git a/src/definition-providers/glimmer-script.ts b/src/definition-providers/glimmer-script.ts index 79a84791..629f9ad6 100644 --- a/src/definition-providers/glimmer-script.ts +++ b/src/definition-providers/glimmer-script.ts @@ -6,156 +6,7 @@ import { parseScriptFile as parse } from 'ember-meta-explorer'; import { containsPosition, toPosition } from './../estree-utils'; import { queryELSAddonsAPIChain } from './../utils/addon-api'; import { Project } from '../project'; -import { getTemplateLocals, preprocess, ASTv1 } from '@glimmer/syntax'; -import { Range as LSRange } from 'vscode-languageserver/node'; - -class TemplateData { - loc: LSRange; - content: string; - constructor(loc: LSRange, content: string) { - this.loc = loc; - this.content = content; - } - get placeholder() { - return `__GLIMMER_TEMPLATE__`; - } - get locals() { - return getTemplateLocals(this.content); - } - get ast(): ASTv1.Template { - return preprocess(this.content); - } -} - -class FileRange { - start = 0; - columns = 0; - characters: string[] = []; - constructor(start = 0) { - this.start = start; - } - addColumn(character = '') { - this.columns++; - this.characters.push(character); - } - get line() { - return this.characters.join(''); - } -} - -export function getFileRanges(file = '') { - const ranges = [new FileRange()]; - - for (let i = 0; i < file.length; i++) { - if (file.charAt(i) !== '\n') { - ranges[ranges.length - 1].addColumn(file.charAt(i)); - } else { - ranges.push(new FileRange(i)); - } - } - - return ranges; -} - -const TEMPLATE_START = ''; -// const HTML_COMMENT_START = ''; -// const HBS_COMMENT_START = '{{!--'; -// const HBS_COMMENT_END = '--}}'; -// const HBS_COMMENT_INLINE_START = '{{! '; -// const HBS_COMMENT_INLINE_END = '}}'; - -const STATE = { - OPEN: 0, - HTML_COMMENT_OPEN: 2, - HTML_COMMENT_CLOSE: 3, - CLOSE: 1, -}; - -class TPosition { - line = 0; - character = 0; - constructor(line = 0, character = 0) { - this.line = line; - this.character = character; - } -} - -export class RangeWalker { - constructor(lines: FileRange[]) { - this.lines = lines; - } - lines: FileRange[] = []; - templates() { - const results: TemplateData[] = []; - let state = STATE.CLOSE; - - let buffer: string[] = []; - - const params = { - start: new TPosition(), - end: new TPosition(), - }; - - const openTemplate = (line: FileRange, offset: number) => { - params.start = new TPosition(this.lines.indexOf(line), offset); - }; - - const completeTemplate = (line: FileRange, offset: number) => { - params.end = new TPosition(this.lines.indexOf(line), offset); - results.push( - new TemplateData( - { - start: params.start, - end: params.end, - }, - buffer.join('') - ) - ); - buffer = []; - }; - - this.lines.forEach((fileRange) => { - let line = fileRange.line; - let offset = 0; - - while (line.length) { - if (state === STATE.CLOSE) { - const index = line.indexOf(TEMPLATE_START); - - if (index === -1) { - return; - } else { - offset = offset + index + TEMPLATE_START.length; - line = line.slice(index + TEMPLATE_START.length); - state = STATE.OPEN; - openTemplate(fileRange, offset); - } - } else if (state === STATE.OPEN) { - const index = line.indexOf(TEMPLATE_END); - - if (index === -1) { - buffer.push(line); - buffer.push('\n'); - - return; - } else { - buffer.push(line.slice(0, index)); - line = line.slice(index + TEMPLATE_END.length); - offset = offset + index; - state = STATE.CLOSE; - completeTemplate(fileRange, offset); - } - } else { - // OOPS - } - } - }); - - return results; - } -} +import { getFileRanges, RangeWalker } from '../utils/glimmer-script'; export default class GlimmerScriptDefinitionProvider { constructor(private server: Server) {} diff --git a/src/utils/glimmer-script.ts b/src/utils/glimmer-script.ts new file mode 100644 index 00000000..96302f60 --- /dev/null +++ b/src/utils/glimmer-script.ts @@ -0,0 +1,177 @@ +import { getTemplateLocals, preprocess, ASTv1 } from '@glimmer/syntax'; +import { Range as LSRange } from 'vscode-languageserver/node'; + +class TemplateData { + loc: LSRange; + content: string; + constructor(loc: LSRange, content: string) { + this.loc = loc; + this.content = content; + } + get placeholder() { + return `__GLIMMER_TEMPLATE__`; + } + get locals() { + return getTemplateLocals(this.content); + } + get ast(): ASTv1.Template { + return preprocess(this.content); + } +} + +class FileRange { + start = 0; + columns = 0; + line = 0; + characters: string[] = []; + constructor(start = 0) { + this.start = start; + } + addColumn(character = '') { + this.columns++; + this.characters.push(character); + } + get content() { + return this.characters.join(''); + } +} + +export function getFileRanges(file = '') { + const ranges = [new FileRange()]; + + let newline = 0; + + for (let i = 0; i < file.length; i++) { + if (file.charAt(i) !== '\n') { + ranges[ranges.length - 1].addColumn(file.charAt(i)); + } else { + ranges.push(new FileRange(i + newline)); + newline++; + } + } + + return ranges.map((e, index) => { + e.line = index + 1; + + return e; + }); +} + +const STATE = { + OPEN: 0, + HTML_COMMENT_OPEN: 2, + HTML_COMMENT_CLOSE: 3, + CLOSE: 1, +}; + +class TPosition { + line = 0; + character = 0; + constructor(line = 0, character = 0) { + this.line = line; + this.character = character; + } +} + +export class RangeWalker { + constructor(lines: FileRange[]) { + this.lines = lines; + } + lines: FileRange[] = []; + extractDocumentPart(includeBounds = false, openTag = '', closeTag = '') { + const results: TemplateData[] = []; + let state = STATE.CLOSE; + + let buffer: string[] = []; + + const params = { + start: new TPosition(), + end: new TPosition(), + }; + + const openTemplate = (line: FileRange, offset: number) => { + params.start = new TPosition(this.lines.indexOf(line), offset); + }; + + const completeTemplate = (line: FileRange, offset: number) => { + params.end = new TPosition(this.lines.indexOf(line), offset); + results.push( + new TemplateData( + { + start: params.start, + end: params.end, + }, + buffer.join('') + ) + ); + buffer = []; + }; + + this.lines.forEach((fileRange) => { + let line = fileRange.content; + let offset = 0; + + while (line.length) { + if (state === STATE.CLOSE) { + const index = line.indexOf(openTag); + + if (index === -1) { + return; + } else { + if (!includeBounds) { + offset = offset + index + openTag.length; + line = line.slice(index + openTag.length); + } else { + offset = offset + index; + line = line.slice(index); + } + + state = STATE.OPEN; + openTemplate(fileRange, offset); + } + } else if (state === STATE.OPEN) { + const index = line.indexOf(closeTag); + + if (index === -1) { + buffer.push(line); + buffer.push('\n'); + + return; + } else { + if (!includeBounds) { + buffer.push(line.slice(0, index)); + line = line.slice(index + closeTag.length); + offset = offset + index; + } else { + buffer.push(line.slice(0, index + closeTag.length)); + line = line.slice(index + closeTag.length); + offset = offset + index + closeTag.length; + } + + state = STATE.CLOSE; + completeTemplate(fileRange, offset); + } + } else { + // OOPS + } + } + }); + + return results; + } + templates() { + return this.extractDocumentPart(false, ''); + } + htmlComments() { + return this.extractDocumentPart(false, ''); + } + hbsComments() { + return this.extractDocumentPart(false, '{{!--', '--}}'); + } + hbsInlineComments() { + return this.extractDocumentPart(false, '{{!', '}}'); + } + styles() { + return this.extractDocumentPart(false, ''); + } +} diff --git a/test/utils/glimmer-script-test.ts b/test/utils/glimmer-script-test.ts new file mode 100644 index 00000000..ec88ec6e --- /dev/null +++ b/test/utils/glimmer-script-test.ts @@ -0,0 +1,41 @@ +import { getFileRanges } from './../../src/utils/glimmer-script'; + +describe('glimmer-scripts', function () { + describe('getFileRanges()', function () { + it('support single line file', function () { + const tpl = ``; + const results = getFileRanges(tpl); + + expect(results.length).toBe(1); + expect(results[0].content).toBe(''); + expect(results[0].start).toBe(0); + expect(results[0].line).toBe(1); + expect(results[0].columns).toBe(tpl.length); + }); + it('support two line file', function () { + const tpl = ``; + const results = getFileRanges(tpl); + + expect(results.length).toBe(2); + expect(results[0].content).toBe('