mirror of
https://github.com/enso-org/enso.git
synced 2024-12-22 22:51:38 +03:00
6881bc844e
When using `npm run lint`, there are a lot of warnings for the last couple of weeks, and it is really annoying because I keep skipping important warnings for my files.
473 lines
16 KiB
TypeScript
473 lines
16 KiB
TypeScript
import * as random from 'lib0/random'
|
|
import {
|
|
Builder,
|
|
EnsoUUID,
|
|
OutboundMessage,
|
|
OutboundPayload,
|
|
VisualizationContext,
|
|
VisualizationUpdate,
|
|
} from 'shared/binaryProtocol'
|
|
import { mockDataWSHandler as originalMockDataWSHandler } from 'shared/dataServer/mock'
|
|
import type {
|
|
ContextId,
|
|
ExpressionId,
|
|
LibraryComponentGroup,
|
|
Path,
|
|
Uuid,
|
|
VisualizationConfiguration,
|
|
response,
|
|
} from 'shared/languageServerTypes'
|
|
import type { SuggestionEntry } from 'shared/languageServerTypes/suggestions'
|
|
import { uuidToBits } from 'shared/uuid'
|
|
import type { MockTransportData, WebSocketHandler } from 'src/util/net'
|
|
import type { QualifiedName } from 'src/util/qualifiedName'
|
|
import { mockFsDirectoryHandle, type FileTree } from '../src/util/convert/fsAccess'
|
|
import mockDb from '../stories/mockSuggestions.json' assert { type: 'json' }
|
|
|
|
const mockProjectId = random.uuidv4() as Uuid
|
|
const standardBase = 'Standard.Base' as QualifiedName
|
|
|
|
export function placeholderGroups(): LibraryComponentGroup[] {
|
|
return [
|
|
{ color: '#4D9A29', name: 'Input', library: standardBase, exports: [] },
|
|
{ color: '#B37923', name: 'Web', library: standardBase, exports: [] },
|
|
{ color: '#9735B9', name: 'Parse', library: standardBase, exports: [] },
|
|
{ color: '#4D9A29', name: 'Select', library: standardBase, exports: [] },
|
|
{ color: '#B37923', name: 'Join', library: standardBase, exports: [] },
|
|
{ color: '#9735B9', name: 'Transform', library: standardBase, exports: [] },
|
|
{ color: '#4D9A29', name: 'Output', library: standardBase, exports: [] },
|
|
]
|
|
}
|
|
|
|
let mainFile = `\
|
|
from Standard.Base import all
|
|
|
|
func1 arg =
|
|
f2 = Main.func2 arg
|
|
result = f2 - 5
|
|
result
|
|
|
|
func2 a =
|
|
r = 42 + a
|
|
r
|
|
|
|
main =
|
|
five = 5
|
|
ten = 10
|
|
sum = five + ten
|
|
prod = sum * 3
|
|
## This node can be entered
|
|
final = Main.func1 prod
|
|
list = []
|
|
text = 'test'
|
|
|
|
# Widget tests
|
|
data = Data.read
|
|
filtered = data.filter
|
|
aggregated = data.aggregate
|
|
`
|
|
|
|
export function getMainFile() {
|
|
return mainFile
|
|
}
|
|
|
|
export function setMainFile(newMainFile: string) {
|
|
return (mainFile = newMainFile)
|
|
}
|
|
|
|
const fileTree = {
|
|
src: {
|
|
get 'Main.enso'() {
|
|
return mainFile
|
|
},
|
|
},
|
|
}
|
|
|
|
const visualizations = new Map<Uuid, VisualizationConfiguration>()
|
|
const visualizationExprIds = new Map<Uuid, ExpressionId>()
|
|
|
|
const encoder = new TextEncoder()
|
|
const encodeJSON = (data: unknown) => encoder.encode(JSON.stringify(data))
|
|
|
|
const scatterplotJson = encodeJSON({
|
|
axis: {
|
|
x: { label: 'x-axis label', scale: 'linear' },
|
|
y: { label: 'y-axis label', scale: 'logarithmic' },
|
|
},
|
|
points: { labels: 'visible' },
|
|
data: [
|
|
{ x: 0.1, y: 0.7, label: 'foo', color: '#FF0000', shape: 'circle', size: 0.2 },
|
|
{ x: 0.4, y: 0.2, label: 'baz', color: '#0000FF', shape: 'square', size: 0.3 },
|
|
],
|
|
})
|
|
|
|
const mockVizData: Record<string, Uint8Array | ((params: string[]) => Uint8Array)> = {
|
|
// JSON
|
|
'Standard.Visualization.Preprocessor.default_preprocessor': scatterplotJson,
|
|
'Standard.Visualization.Scatter_Plot.process_to_json_text': scatterplotJson,
|
|
'Standard.Visualization.SQL.Visualization.prepare_visualization': encodeJSON({
|
|
dialect: 'sql',
|
|
code: `SELECT * FROM \`foo\` WHERE \`a\` = ? AND b LIKE ?;`,
|
|
interpolations: [
|
|
// eslint-disable-next-line camelcase
|
|
{ enso_type: 'Data.Numbers.Number', value: '123' },
|
|
// eslint-disable-next-line camelcase
|
|
{ enso_type: 'Builtins.Main.Text', value: "a'bcd" },
|
|
],
|
|
}),
|
|
'Standard.Visualization.Geo_Map.process_to_json_text': encodeJSON({
|
|
latitude: 37.8,
|
|
longitude: -122.45,
|
|
zoom: 15,
|
|
controller: true,
|
|
showingLabels: true, // Enables presenting labels when hovering over a point.
|
|
layers: [
|
|
{
|
|
type: 'Scatterplot_Layer',
|
|
data: [
|
|
{
|
|
latitude: 37.8,
|
|
longitude: -122.45,
|
|
color: [255, 0, 0],
|
|
radius: 100,
|
|
label: 'an example label',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}),
|
|
'Standard.Visualization.Histogram.process_to_json_text': encodeJSON({
|
|
axis: {
|
|
x: { label: 'x-axis label', scale: 'linear' },
|
|
y: { label: 'y-axis label', scale: 'logarithmic' },
|
|
},
|
|
color: 'rgb(1.0,0.0,0.0)',
|
|
bins: 10,
|
|
data: {
|
|
values: [0.1, 0.2, 0.1, 0.15, 0.7],
|
|
},
|
|
}),
|
|
'Standard.Visualization.Table.Visualization.prepare_visualization': encodeJSON({
|
|
type: 'Matrix',
|
|
// eslint-disable-next-line camelcase
|
|
column_count: 5,
|
|
// eslint-disable-next-line camelcase
|
|
all_rows_count: 10,
|
|
json: Array.from({ length: 10 }, (_, i) => Array.from({ length: 5 }, (_, j) => `${i},${j}`)),
|
|
}),
|
|
'Standard.Visualization.Warnings.process_to_json_text': encodeJSON([
|
|
'warning 1',
|
|
"warning 2!!&<>;'\x22",
|
|
]),
|
|
'Standard.Visualization.Widgets.get_widget_json': (params) => {
|
|
switch (params[0]) {
|
|
case '.read':
|
|
return encodeJSON([
|
|
[
|
|
'path',
|
|
{
|
|
type: 'Widget',
|
|
constructor: 'Single_Choice',
|
|
label: null,
|
|
values: [
|
|
{
|
|
type: 'Choice',
|
|
constructor: 'Option',
|
|
value: '"File 1"',
|
|
label: 'File 1',
|
|
parameters: [],
|
|
},
|
|
{
|
|
type: 'Choice',
|
|
constructor: 'Option',
|
|
value: '"File 2"',
|
|
label: 'File 2',
|
|
parameters: [],
|
|
},
|
|
],
|
|
display: { type: 'Display', constructor: 'Always' },
|
|
},
|
|
],
|
|
])
|
|
case '.aggregate':
|
|
return encodeJSON([
|
|
[
|
|
'columns',
|
|
{
|
|
type: 'Widget',
|
|
constructor: 'Vector_Editor',
|
|
/* eslint-disable camelcase */
|
|
item_default: 'Aggregate_Column.Group_By',
|
|
item_editor: {
|
|
type: 'Widget',
|
|
constructor: 'Single_Choice',
|
|
label: null,
|
|
values: [
|
|
{
|
|
type: 'Choice',
|
|
constructor: 'Option',
|
|
value: 'Standard.Table.Data.Aggregate_Column.Aggregate_Column.Group_By',
|
|
label: null,
|
|
parameters: [
|
|
[
|
|
'column',
|
|
{
|
|
type: 'Widget',
|
|
constructor: 'Single_Choice',
|
|
label: null,
|
|
values: [
|
|
{
|
|
type: 'Choice',
|
|
constructor: 'Option',
|
|
value: '"column 1"',
|
|
label: 'column 1',
|
|
parameters: [],
|
|
},
|
|
{
|
|
type: 'Choice',
|
|
constructor: 'Option',
|
|
value: '"column 2"',
|
|
label: 'column 2',
|
|
parameters: [],
|
|
},
|
|
],
|
|
display: { type: 'Display', constructor: 'Always' },
|
|
},
|
|
],
|
|
],
|
|
},
|
|
{
|
|
type: 'Choice',
|
|
constructor: 'Option',
|
|
value: 'Standard.Table.Data.Aggregate_Column.Aggregate_Column.Count',
|
|
label: null,
|
|
parameters: [],
|
|
},
|
|
{
|
|
type: 'Choice',
|
|
constructor: 'Option',
|
|
value: 'Standard.Table.Data.Aggregate_Column.Aggregate_Column.Count_Distinct',
|
|
label: null,
|
|
parameters: [
|
|
[
|
|
'columns',
|
|
{
|
|
type: 'Widget',
|
|
constructor: 'Single_Choice',
|
|
label: null,
|
|
values: [
|
|
{
|
|
type: 'Choice',
|
|
constructor: 'Option',
|
|
value: '"column 1"',
|
|
label: 'column 1',
|
|
parameters: [],
|
|
},
|
|
{
|
|
type: 'Choice',
|
|
constructor: 'Option',
|
|
value: '"column 2"',
|
|
label: 'column 2',
|
|
parameters: [],
|
|
},
|
|
],
|
|
display: { type: 'Display', constructor: 'Always' },
|
|
},
|
|
],
|
|
],
|
|
},
|
|
],
|
|
display: { type: 'Display', constructor: 'Always' },
|
|
},
|
|
/* eslint-enable camelcase */
|
|
display: { type: 'Display', constructor: 'Always' },
|
|
},
|
|
],
|
|
])
|
|
default:
|
|
return encodeJSON([])
|
|
}
|
|
},
|
|
// The following visualizations do not have unique transformation methods, and as such are only kept
|
|
// for posterity.
|
|
Image: encodeJSON({
|
|
mediaType: 'image/svg+xml',
|
|
base64: `PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0\
|
|
MCI+PGcgY2xpcC1wYXRoPSJ1cmwoI2EpIj48cGF0aCBkPSJNMjAuMDUgMEEyMCAyMCAwIDAgMCAwIDIwLjA1IDIwLjA2IDIwLjA\
|
|
2IDAgMSAwIDIwLjA1IDBabTAgMzYuMDVjLTguOTMgMC0xNi4xLTcuMTctMTYuMS0xNi4xIDAtOC45NCA3LjE3LTE2LjEgMTYuMS\
|
|
0xNi4xIDguOTQgMCAxNi4xIDcuMTYgMTYuMSAxNi4xYTE2LjE4IDE2LjE4IDAgMCAxLTE2LjEgMTYuMVoiLz48cGF0aCBkPSJNM\
|
|
jcuMTIgMTcuNzdhNC42OCA0LjY4IDAgMCAxIDIuMzkgNS45MiAxMC4yMiAxMC4yMiAwIDAgMS05LjU2IDYuODZBMTAuMiAxMC4y\
|
|
IDAgMCAxIDkuNzcgMjAuMzZzMS41NSAyLjA4IDQuNTcgMi4wOGMzLjAxIDAgNC4zNi0xLjE0IDUuNi0yLjA4IDEuMjUtLjkzIDI\
|
|
uMDktMyA1LjItMyAuNzMgMCAxLjQ2LjIgMS45OC40WiIvPjwvZz48ZGVmcz48Y2xpcFBhdGggaWQ9ImEiPjxwYXRoIGZpbGw9Ii\
|
|
NmZmYiIGQ9Ik0wIDBoNDB2NDBIMHoiLz48L2NsaXBQYXRoPjwvZGVmcz48L3N2Zz4=`,
|
|
}),
|
|
Heatmap: encodeJSON([
|
|
['A', 'B', 'C', 'D', 'A'],
|
|
['D', 'E', 'D', 'X', 'Z'],
|
|
[50, 25, 40, 20, 10],
|
|
]),
|
|
}
|
|
|
|
function createMessageId(builder: Builder) {
|
|
const messageUuid = random.uuidv4()
|
|
const [leastSigBits, mostSigBits] = uuidToBits(messageUuid)
|
|
return EnsoUUID.createEnsoUUID(builder, leastSigBits, mostSigBits)
|
|
}
|
|
|
|
function createId(id: Uuid) {
|
|
const [low, high] = uuidToBits(id)
|
|
return (builder: Builder) => EnsoUUID.createEnsoUUID(builder, low, high)
|
|
}
|
|
|
|
function sendVizData(id: Uuid, config: VisualizationConfiguration) {
|
|
const vizDataHandler =
|
|
mockVizData[
|
|
typeof config.expression === 'string'
|
|
? `${config.visualizationModule}.${config.expression}`
|
|
: `${config.expression.definedOnType}.${config.expression.name}`
|
|
]
|
|
if (!vizDataHandler || !sendData) return
|
|
const vizData =
|
|
vizDataHandler instanceof Uint8Array
|
|
? vizDataHandler
|
|
: vizDataHandler(config.positionalArgumentsExpressions ?? [])
|
|
const builder = new Builder()
|
|
const exprId = visualizationExprIds.get(id)
|
|
const visualizationContextOffset = VisualizationContext.createVisualizationContext(
|
|
builder,
|
|
createId(id),
|
|
createId(config.executionContextId),
|
|
exprId ? createId(exprId) : null,
|
|
)
|
|
const dataOffset = VisualizationUpdate.createDataVector(builder, vizData)
|
|
const payload = VisualizationUpdate.createVisualizationUpdate(
|
|
builder,
|
|
visualizationContextOffset,
|
|
dataOffset,
|
|
)
|
|
const rootTable = OutboundMessage.createOutboundMessage(
|
|
builder,
|
|
createMessageId,
|
|
null, // correlationId
|
|
OutboundPayload.VISUALIZATION_UPDATE,
|
|
payload,
|
|
)
|
|
sendData(builder.finish(rootTable).toArrayBuffer())
|
|
}
|
|
|
|
let sendData: ((data: string | Blob | ArrayBufferLike | ArrayBufferView) => void) | undefined
|
|
|
|
export const mockLSHandler: MockTransportData = async (method, data, transport) => {
|
|
switch (method) {
|
|
case 'session/initProtocolConnection':
|
|
return {
|
|
contentRoots: [{ type: 'Project', id: mockProjectId }],
|
|
} satisfies response.InitProtocolConnection
|
|
case 'executionContext/create': {
|
|
const data_ = data as {
|
|
contextId: ContextId
|
|
}
|
|
setTimeout(
|
|
() => transport.emit('executionContext/executionComplete', { contextId: data_.contextId }),
|
|
100,
|
|
)
|
|
return {
|
|
contextId: data_.contextId,
|
|
}
|
|
}
|
|
case 'executionContext/attachVisualization': {
|
|
const data_ = data as {
|
|
visualizationId: Uuid
|
|
expressionId: ExpressionId
|
|
visualizationConfig: VisualizationConfiguration
|
|
}
|
|
visualizations.set(data_.visualizationId, data_.visualizationConfig)
|
|
visualizationExprIds.set(data_.visualizationId, data_.expressionId)
|
|
sendVizData(data_.visualizationId, data_.visualizationConfig)
|
|
return
|
|
}
|
|
case 'executionContext/detachVisualization': {
|
|
const data_ = data as {
|
|
visualizationId: Uuid
|
|
expressionId: ExpressionId
|
|
contextId: ContextId
|
|
}
|
|
visualizations.delete(data_.visualizationId)
|
|
visualizationExprIds.delete(data_.visualizationId)
|
|
return
|
|
}
|
|
case 'executionContext/modifyVisualization': {
|
|
const data_ = data as {
|
|
visualizationId: Uuid
|
|
visualizationConfig: VisualizationConfiguration
|
|
}
|
|
visualizations.set(data_.visualizationId, data_.visualizationConfig)
|
|
sendVizData(data_.visualizationId, data_.visualizationConfig)
|
|
return
|
|
}
|
|
case 'search/getSuggestionsDatabase':
|
|
return {
|
|
entries: mockDb.map((suggestion, id) => ({
|
|
id,
|
|
suggestion: suggestion as SuggestionEntry,
|
|
})),
|
|
currentVersion: 1,
|
|
} satisfies response.GetSuggestionsDatabase
|
|
case 'runtime/getComponentGroups':
|
|
return { componentGroups: placeholderGroups() } satisfies response.GetComponentGroups
|
|
case 'executionContext/push':
|
|
case 'executionContext/pop':
|
|
case 'executionContext/recompute':
|
|
case 'capability/acquire':
|
|
return {}
|
|
case 'file/list': {
|
|
const data_ = data as { path: Path }
|
|
if (!data_.path) return Promise.reject(`'path' parameter missing in '${method}'`)
|
|
if (data_.path.rootId !== mockProjectId)
|
|
return Promise.reject(
|
|
`Only the project's 'rootId' is supported, got '${data_.path.rootId}'`,
|
|
)
|
|
let child: FileTree | string | ArrayBuffer | undefined = fileTree
|
|
if (child) {
|
|
for (const segment of data_.path.segments) {
|
|
child = child?.[segment]
|
|
if (!child || typeof child === 'string' || child instanceof ArrayBuffer) break
|
|
}
|
|
}
|
|
if (!child) return Promise.reject(`Folder '/${data_.path.segments.join('/')}' not found.`)
|
|
if (typeof child === 'string' || child instanceof ArrayBuffer)
|
|
return Promise.reject(`File '/${data_.path.segments.join('/')}' is not a folder.`)
|
|
return {
|
|
paths: Object.entries(child).map(([name, entry]) => ({
|
|
type: typeof entry === 'string' || entry instanceof ArrayBuffer ? 'File' : 'Directory',
|
|
name,
|
|
path: { rootId: data_.path.rootId, segments: [...data_.path.segments, name] },
|
|
})),
|
|
} satisfies response.FileList
|
|
}
|
|
default:
|
|
return Promise.reject(`Method '${method}' not mocked`)
|
|
}
|
|
}
|
|
|
|
const directory = mockFsDirectoryHandle(fileTree, '(root)')
|
|
|
|
export const mockDataHandler: WebSocketHandler = originalMockDataWSHandler(
|
|
async (segments) => {
|
|
if (!segments.length) return
|
|
let file
|
|
try {
|
|
let dir = directory
|
|
for (const segment of segments.slice(0, -1)) {
|
|
dir = await dir.getDirectoryHandle(segment)
|
|
}
|
|
const fileHandle = await dir.getFileHandle(segments.at(-1)!)
|
|
file = await fileHandle.getFile()
|
|
} catch {
|
|
return
|
|
}
|
|
return await file?.arrayBuffer()
|
|
},
|
|
(send) => (sendData = send),
|
|
)
|