diff --git a/CHANGELOG.md b/CHANGELOG.md index 73e8f9102e1..d10224a316b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,11 @@ #### Enso IDE - ["Add node" button is not obscured by output port][10433] +- [Numeric Widget does not accept non-numeric input][10457]. This is to prevent + node being completely altered by accidental code put to the widget. [10433]: https://github.com/enso-org/enso/pull/10443 +[10457]: https://github.com/enso-org/enso/pull/10457 #### Enso Enso Standard Library diff --git a/app/gui2/rust-ffi/src/lib.rs b/app/gui2/rust-ffi/src/lib.rs index e33e1400c13..55ece57b997 100644 --- a/app/gui2/rust-ffi/src/lib.rs +++ b/app/gui2/rust-ffi/src/lib.rs @@ -37,6 +37,22 @@ pub fn is_ident_or_operator(code: &str) -> u32 { } } +#[wasm_bindgen] +pub fn is_numeric_literal(code: &str) -> bool { + let parsed = PARSER.with(|parser| parser.run(code)); + let enso_parser::syntax::tree::Variant::BodyBlock(body) = *parsed.variant else { return false }; + let [stmt] = &body.statements[..] else { return false }; + stmt.expression.as_ref().map_or(false, |expr| match &*expr.variant { + enso_parser::syntax::tree::Variant::Number(_) => true, + enso_parser::syntax::tree::Variant::UnaryOprApp(app) => + app.opr.code == "-" + && app.rhs.as_ref().map_or(false, |rhs| { + matches!(*rhs.variant, enso_parser::syntax::tree::Variant::Number(_)) + }), + _ => false, + }) +} + #[wasm_bindgen(start)] fn main() { console_error_panic_hook::set_once(); diff --git a/app/gui2/shared/ast/ffi.ts b/app/gui2/shared/ast/ffi.ts index 8ff6078fc60..d0903325f9f 100644 --- a/app/gui2/shared/ast/ffi.ts +++ b/app/gui2/shared/ast/ffi.ts @@ -6,7 +6,12 @@ import { createXXHash128 } from 'hash-wasm' import type { IDataType } from 'hash-wasm/dist/lib/util' -import init, { is_ident_or_operator, parse, parse_doc_to_json } from '../../rust-ffi/pkg/rust_ffi' +import init, { + is_ident_or_operator, + is_numeric_literal, + parse, + parse_doc_to_json, +} from '../../rust-ffi/pkg/rust_ffi' import { assertDefined } from '../util/assert' import { isNode } from '../util/detect' @@ -37,4 +42,4 @@ export async function initializeFFI(path?: string | undefined) { // await initializeFFI() /* eslint-disable-next-line camelcase */ -export { is_ident_or_operator, parse_doc_to_json, parse as parse_tree } +export { is_ident_or_operator, is_numeric_literal, parse_doc_to_json, parse as parse_tree } diff --git a/app/gui2/shared/ast/ffiPolyglot.ts b/app/gui2/shared/ast/ffiPolyglot.ts index 8fb372672c8..7dd6c3b990f 100644 --- a/app/gui2/shared/ast/ffiPolyglot.ts +++ b/app/gui2/shared/ast/ffiPolyglot.ts @@ -12,10 +12,17 @@ declare global { function parse_tree(code: string): Uint8Array function parse_doc_to_json(docs: string): string function is_ident_or_operator(code: string): number + function is_numeric_literal(code: string): boolean function xxHash128(input: IDataType): string } export async function initializeFFI(_path?: string | undefined) {} /* eslint-disable-next-line camelcase */ -export const { is_ident_or_operator, parse_doc_to_json, parse_tree, xxHash128 } = globalThis +export const { + is_ident_or_operator, + is_numeric_literal, + parse_doc_to_json, + parse_tree, + xxHash128, +} = globalThis diff --git a/app/gui2/shared/ast/tree.ts b/app/gui2/shared/ast/tree.ts index 757fa775949..1f90f1b84c3 100644 --- a/app/gui2/shared/ast/tree.ts +++ b/app/gui2/shared/ast/tree.ts @@ -32,6 +32,7 @@ import type { SourceRangeEdit } from '../util/data/text' import { allKeys } from '../util/types' import type { ExternalId, VisualizationMetadata } from '../yjsModel' import { visMetadataEquals } from '../yjsModel' +import { is_numeric_literal } from './ffi' import * as RawAst from './generated/ast' import { applyTextEditsToAst, @@ -1825,6 +1826,10 @@ export class MutableNumericLiteral extends NumericLiteral implements MutableAst export interface MutableNumericLiteral extends NumericLiteral, MutableAst {} applyMixins(MutableNumericLiteral, [MutableAst]) +export function isNumericLiteral(code: string) { + return is_numeric_literal(code) +} + /** The actual contents of an `ArgumentDefinition` are complex, but probably of more interest to the compiler than the * GUI. We just need to represent them faithfully and create the simple cases. */ type ArgumentDefinition = (T['ast'] | T['token'])[] diff --git a/app/gui2/src/components/GraphEditor/widgets/WidgetNumber.vue b/app/gui2/src/components/GraphEditor/widgets/WidgetNumber.vue index eddcf303454..74f53421462 100644 --- a/app/gui2/src/components/GraphEditor/widgets/WidgetNumber.vue +++ b/app/gui2/src/components/GraphEditor/widgets/WidgetNumber.vue @@ -10,9 +10,9 @@ import { computed, ref, type ComponentInstance } from 'vue' const props = defineProps(widgetProps(widgetDefinition)) const inputComponent = ref>() -function setValue(value: number | string) { +function setValue(value: string | undefined) { props.onUpdate({ - portUpdate: { value: value.toString(), origin: props.input.portId }, + portUpdate: { value, origin: props.input.portId }, }) } diff --git a/app/gui2/src/components/widgets/NumericInputWidget.vue b/app/gui2/src/components/widgets/NumericInputWidget.vue index 504ecdc06f9..f1fcac888a0 100644 --- a/app/gui2/src/components/widgets/NumericInputWidget.vue +++ b/app/gui2/src/components/widgets/NumericInputWidget.vue @@ -1,5 +1,6 @@