Skip to content

Commit 9a82696

Browse files
authored
Merge pull request #608 from forcedotcom/sm/staticly-bundleable
@W-18841416 feat: statically bundleable
2 parents 76c1587 + 4e6e656 commit 9a82696

File tree

4 files changed

+74
-131
lines changed

4 files changed

+74
-131
lines changed

packages/aura-language-server/src/tern-server/__tests__/__snapshots__/ternCompletion.spec.ts.snap

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ exports[`tern completions 1`] = `
7676
"kind": 18,
7777
"label": "closed",
7878
},
79+
{
80+
"detail": "?",
81+
"documentation": undefined,
82+
"kind": 18,
83+
"label": "components",
84+
},
7985
{
8086
"detail": "fn(message: string) -> bool",
8187
"documentation": "Displays a modal dialog with a message and two buttons, OK and Cancel.",
@@ -222,6 +228,12 @@ It should be noted that data stored in either localStorage or sessionStorage is
222228
"kind": 18,
223229
"label": "location",
224230
},
231+
{
232+
"detail": "?",
233+
"documentation": undefined,
234+
"kind": 18,
235+
"label": "message",
236+
},
225237
{
226238
"detail": "{exports}",
227239
"documentation": undefined,
@@ -524,6 +536,12 @@ It should be noted that data stored in either localStorage or sessionStorage is
524536
"kind": 3,
525537
"label": "requestAnimationFrame",
526538
},
539+
{
540+
"detail": "?",
541+
"documentation": undefined,
542+
"kind": 18,
543+
"label": "returnValue",
544+
},
527545
{
528546
"detail": "screen",
529547
"documentation": "Returns a reference to the screen object associated with the window.",
@@ -610,6 +628,12 @@ It should be noted that data stored in either sessionStorage or localStorage is
610628
"kind": 3,
611629
"label": "setTimeout",
612630
},
631+
{
632+
"detail": "string",
633+
"documentation": undefined,
634+
"kind": 18,
635+
"label": "state",
636+
},
613637
{
614638
"detail": "<top>",
615639
"documentation": "Returns a reference to the topmost window in the window hierarchy.",

packages/aura-language-server/src/tern-server/tern-aura.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as infer from '../tern/lib/infer';
33
import * as tern from '../tern/lib/tern';
44
import * as walk from 'acorn-walk';
55
import * as fs from 'fs';
6-
import * as path from 'path';
6+
import defs from './aura_types.json';
77

88
const WG_DEFAULT_EXPORT = 95;
99
let server: any = {};
@@ -135,12 +135,6 @@ function readFileAsync(filename, c): void {
135135
});
136136
}
137137

138-
function loadDefs(): void {
139-
let defs = fs.readFileSync(path.join(__dirname, 'aura_types.json'), 'utf8');
140-
defs = JSON.parse(defs);
141-
server.addDefs(defs);
142-
}
143-
144138
function findAndBindComponent(type, server, cx, infer): void {
145139
const evs = cx.props['Component'];
146140
if (!evs) {
@@ -411,7 +405,7 @@ tern.registerPlugin('aura', function(s, options) {
411405
});
412406

413407
_debug('IDE mode');
414-
loadDefs();
408+
server.addDefs(defs);
415409

416410
_debug(new Date().toISOString() + ' Done loading!');
417411
});

packages/aura-language-server/src/tern-server/tern-server.ts

Lines changed: 47 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import fs, { readFileSync, readdirSync, statSync } from 'fs';
1+
import fs, { readFileSync } from 'fs';
22
import * as tern from '../tern/lib/tern';
33
import path from 'path';
44
import * as util from 'util';
55
import * as infer from '../tern/lib/infer';
66
import LineColumnFinder from 'line-column';
77
import { findPreviousWord, findPreviousLeftParan, countPreviousCommas } from './string-util';
88
import URI from 'vscode-uri';
9+
import browser from '../tern/defs/browser.json';
10+
import ecmascript from '../tern/defs/ecmascript.json';
911

1012
import { memoize } from '@salesforce/lightning-lsp-common/lib/utils';
1113
import {
@@ -43,7 +45,7 @@ interface TernServer extends tern.Server {
4345
* The `callback` function will be called when the request completes. If an `error` occurred,
4446
* it will be passed as a first argument. Otherwise, the `response` (parsed) JSON object will be passed as second argument.
4547
*
46-
* When the server hasnt been configured to be asynchronous, the callback will be called before request returns.
48+
* When the server hasn't been configured to be asynchronous, the callback will be called before request returns.
4749
*/
4850
request(doc: any, callback: any): void;
4951
}
@@ -57,9 +59,6 @@ let ternServer: TernServer;
5759
let asyncTernRequest;
5860
let asyncFlush;
5961

60-
const defaultLibs = ['browser', 'ecmascript'];
61-
const defaultPlugins = { modules: {}, aura: {}, doc_comment: {} };
62-
6362
const defaultConfig = {
6463
ecmaVersion: 6,
6564
stripCRs: false,
@@ -70,107 +69,33 @@ const defaultConfig = {
7069
dependencyBudget: 20000,
7170
};
7271

73-
function readJSON(fileName): any {
74-
const file = fs.readFileSync(fileName, 'utf-8');
75-
try {
76-
return JSON.parse(file);
77-
} catch (e) {
78-
console.warn('Bad JSON in ' + fileName + ': ' + e.message);
79-
}
80-
}
81-
82-
function findDefs(libs): any[] {
83-
const ternlibpath = require.resolve('../tern/lib/tern');
84-
const ternbasedir = path.join(ternlibpath, '..', '..');
85-
86-
const defs = [];
87-
const src = libs.slice();
88-
for (let file of src) {
89-
console.log(`Loading support library: ${file}`);
90-
if (!/\.json$/.test(file)) {
91-
file = file + '.json';
92-
}
93-
const def = path.join(ternbasedir, 'defs', file);
94-
if (fs.existsSync(def)) {
95-
defs.push(readJSON(def));
96-
} else {
97-
console.log(`Not found: ${file}`);
98-
}
99-
}
100-
return defs;
101-
}
102-
103-
async function loadLocal(plugin, rootPath): Promise<boolean> {
104-
let found;
105-
try {
106-
// local resolution only here
107-
found = require.resolve('./tern-' + plugin);
108-
} catch (e) {
109-
return false;
110-
}
111-
112-
const mod = await import(found);
113-
if (mod.hasOwnProperty('initialize')) {
114-
mod.initialize(rootPath);
115-
}
116-
return true;
117-
}
72+
const auraInstanceLastSort = (a: string, b: string): number =>
73+
a.endsWith('AuraInstance.js') === b.endsWith('AuraInstance.js') ? 0 : a.endsWith('AuraInstance.js') ? 1 : -1;
11874

119-
async function loadBuiltIn(plugin: string, rootPath: string): Promise<boolean> {
120-
const ternlibpath = require.resolve('../tern/lib/tern');
121-
const ternbasedir = path.join(ternlibpath, '..', '..');
75+
async function loadPlugins(): Promise<{ aura: true; modules: true; doc_comment: true }> {
76+
await import('./tern-aura');
77+
await import('../tern/plugin/modules');
78+
await import('../tern/plugin/doc_comment');
12279

123-
const def = path.join(ternbasedir, 'plugin', plugin);
124-
125-
let found: string;
126-
try {
127-
// local resolution only here
128-
found = require.resolve(def);
129-
} catch (e) {
130-
process.stderr.write('Failed to find plugin ' + plugin + '.\n');
131-
return false;
132-
}
133-
134-
const mod = await import(found);
135-
if (mod.hasOwnProperty('initialize')) {
136-
mod.initialize(rootPath);
137-
}
138-
return true;
80+
return {
81+
aura: true,
82+
modules: true,
83+
doc_comment: true,
84+
};
13985
}
14086

141-
async function loadPlugins(plugins, rootPath): Promise<{}> {
142-
const options = {};
143-
for (const plugin of Object.keys(plugins)) {
144-
const val = plugins[plugin];
145-
if (!val) {
146-
continue;
147-
}
148-
149-
if (!(await loadLocal(plugin, rootPath))) {
150-
if (!(await loadBuiltIn(plugin, rootPath))) {
151-
process.stderr.write('Failed to find plugin ' + plugin + '.\n');
152-
}
153-
}
154-
155-
options[path.basename(plugin)] = true;
87+
/** recursively search upward from the starting diretory. Handling the is it a monorepo vs. packaged vs. bundled code */
88+
const searchAuraResourcesPath = (dir: string): string => {
89+
console.log(`aura-language-server: searching for resources/aura in ${dir}`);
90+
if (fs.existsSync(path.join(dir, 'resources', 'aura'))) {
91+
console.log('found resources/aura in', dir);
92+
return path.join(dir, 'resources', 'aura');
15693
}
157-
158-
return options;
159-
}
160-
161-
function* walkSync(dir: string): any {
162-
const files = readdirSync(dir);
163-
164-
for (const file of files) {
165-
const pathToFile = path.join(dir, file);
166-
const isDirectory = statSync(pathToFile).isDirectory();
167-
if (isDirectory) {
168-
yield* walkSync(pathToFile);
169-
} else {
170-
yield pathToFile;
171-
}
94+
if (path.dirname(dir) === dir) {
95+
throw new Error('No resources/aura directory found');
17296
}
173-
}
97+
return searchAuraResourcesPath(path.dirname(dir));
98+
};
17499

175100
async function ternInit(): Promise<void> {
176101
await asyncTernRequest({
@@ -180,29 +105,27 @@ async function ternInit(): Promise<void> {
180105
// shouldFilter: true,
181106
},
182107
});
183-
const resources = path.join(__dirname, '..', '..', 'resources', 'aura');
184-
const found = [...walkSync(resources)];
185-
let [lastFile, lastText] = [undefined, undefined];
186-
for (const file of found) {
187-
if (file.endsWith('.js')) {
188-
const data = readFileSync(file, 'utf-8');
189-
// HACK HACK HACK - glue it all together baby!
190-
if (file.endsWith('AuraInstance.js')) {
191-
lastFile = file;
192-
lastText = data.concat(`\nwindow['$A'] = new AuraInstance();\n`);
193-
} else {
194-
ternServer.addFile(file, data);
195-
}
196-
}
197-
}
198-
ternServer.addFile(lastFile, lastText);
108+
const resources = searchAuraResourcesPath(__dirname);
109+
(await fs.promises.readdir(resources, { withFileTypes: true, recursive: true }))
110+
.filter(dirent => dirent.isFile() && dirent.name.endsWith('.js'))
111+
.map(dirent => path.join(dirent.parentPath, dirent.name))
112+
// special handling for hacking one snowflake file that needs to go last
113+
.sort(auraInstanceLastSort)
114+
.map(file => ({
115+
file,
116+
contents: file.endsWith('AuraInstance.js')
117+
? // and the snowflake needs to me modified
118+
readFileSync(file, 'utf-8').concat(`\nwindow['$A'] = new AuraInstance();\n`)
119+
: readFileSync(file, 'utf-8'),
120+
}))
121+
.map(({ file, contents }) => ternServer.addFile(file, contents));
199122
}
200123

201124
const init = memoize(ternInit);
202125

203126
export async function startServer(rootPath: string, wsroot: string): tern.Server {
204-
const defs = findDefs(defaultLibs);
205-
const plugins = await loadPlugins(defaultPlugins, rootPath);
127+
const defs = [browser, ecmascript];
128+
const plugins = await loadPlugins();
206129
const config: tern.ConstructorOptions = {
207130
...defaultConfig,
208131
defs,
@@ -239,6 +162,12 @@ function fileToUri(file: string): string {
239162
}
240163
}
241164

165+
function uriToFile(uri: string): string {
166+
const parsedUri = URI.parse(uri);
167+
// paths from tests can be relative or absolute
168+
return parsedUri.scheme ? parsedUri.fsPath : uri;
169+
}
170+
242171
function tern2lspRange({ start, end }: { start: tern.Position; end: tern.Position }): Range {
243172
return {
244173
start: tern2lspPos(start),
@@ -253,10 +182,6 @@ function tern2lspLocation({ file, start, end }: { file: string; start: tern.Posi
253182
};
254183
}
255184

256-
function uriToFile(uri: string): string {
257-
return URI.parse(uri).fsPath;
258-
}
259-
260185
async function ternRequest(event: TextDocumentPositionParams, type: string, options: any = {}): Promise<any> {
261186
return await asyncTernRequest({
262187
query: {

packages/aura-language-server/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"noImplicitAny": false,
1515
"noUnusedParameters": false,
1616
"noUnusedLocals": false,
17-
17+
"resolveJsonModule": true,
1818
"pretty": true,
1919
"declaration": true,
2020
"esModuleInterop": true,

0 commit comments

Comments
 (0)