Skip to content
This repository has been archived by the owner on Jul 24, 2024. It is now read-only.

Commit

Permalink
feat: wire up linting support; fixes #268; relates to #254
Browse files Browse the repository at this point in the history
  • Loading branch information
gashcrumb committed May 13, 2019
1 parent 0faba62 commit 52d4742
Show file tree
Hide file tree
Showing 16 changed files with 676 additions and 18 deletions.
1 change: 1 addition & 0 deletions app/ui-react/packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@storybook/react": "^5.0.11",
"@storybook/theming": "^5.0.11",
"@types/classnames": "^2.2.6",
"@types/codemirror": "^0.0.74",
"@types/expect": "^1.20.3",
"@types/jest": "^24.0.11",
"@types/patternfly-react": "*",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { Text, TextContent } from '@patternfly/react-core';
import * as React from 'react';
import { TextEditor } from '../../../../Shared';
import { ITextEditor, TextEditor } from '../../../../Shared';
import { TemplateType } from './TemplateStepTypeSelector';

interface ITemplateStepTemplateEditorProps {
mode: TemplateType;
textEditorDescription: React.ReactNode;
onChange: (editor: any, data: any, value: string) => void;
onChange: (editor: ITextEditor, data: any, value: string) => void;
initialValue: string;
i18nFileUploadLimit: string;
editorDidMount?: (editor: ITextEditor) => void;
onUpdateLinting?: (
unsortedAnnotations: any[],
annotations: any[],
editor: ITextEditor
) => void;
}

export class TemplateStepTemplateEditor extends React.Component<
Expand All @@ -17,9 +23,13 @@ export class TemplateStepTemplateEditor extends React.Component<
public render() {
const editorOptions = {
gutters: ['CodeMirror-lint-markers'],
lineNumbers: false,
lineNumbers: true,
lineWrapping: true,
lint: true,
lint: {
lintOnChange: false,
onUpdateLinting: this.props.onUpdateLinting,
tooltips: 'gutter',
},
mode: this.props.mode,
readOnly: false,
showCursorWhenSelecting: true,
Expand All @@ -42,6 +52,7 @@ export class TemplateStepTemplateEditor extends React.Component<
onChange={this.props.onChange}
options={editorOptions}
value={this.props.initialValue}
editorDidMount={this.props.editorDidMount}
/>
</>
);
Expand Down
15 changes: 12 additions & 3 deletions app/ui-react/packages/ui/src/Shared/TextEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import * as CodeMirror from 'codemirror';
import * as React from 'react';
import { ICodeMirror, UnControlled as CodeMirror } from 'react-codemirror2';
import { UnControlled as ReactCodeMirror } from 'react-codemirror2';

import 'codemirror/addon/display/placeholder.js';
import 'codemirror/addon/lint/lint.css';
import 'codemirror/addon/lint/lint.js';
import 'codemirror/addon/mode/overlay.js';
import 'codemirror/lib/codemirror.css';
import 'codemirror/mode/velocity/velocity.js';

export type ITextEditor = ICodeMirror;
export { CodeMirror };
export type ITextEditor = CodeMirror.Editor;

export interface ITextEditorProps {
value: string;
options: { [name: string]: any };
onChange: (editor: ITextEditor, data: any, value: string) => void;
editorDidMount?: (editor: ITextEditor) => void;
}

export class TextEditor extends React.Component<ITextEditorProps> {
Expand All @@ -17,10 +25,11 @@ export class TextEditor extends React.Component<ITextEditorProps> {
const options = { ...this.props.options };
return (
<>
<CodeMirror
<ReactCodeMirror
value={this.props.value}
options={options}
onChange={this.props.onChange}
editorDidMount={this.props.editorDidMount}
/>
</>
);
Expand Down
6 changes: 5 additions & 1 deletion app/ui-react/packages/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@babel/core": "^7.1.5",
"@types/expect": "^1.20.3",
"@types/jest": "^24.0.11",
"@types/mustache": "^0.8.32",
"@types/patternfly-react": "*",
"@types/react": "^16.4.18",
"@types/react-dom": "^16.0.9",
Expand Down Expand Up @@ -54,9 +55,12 @@
"@syndesis/auto-form": "*",
"@syndesis/models": "*",
"@syndesis/ui": "*",
"freemarker-parser": "1.1.6",
"moment": "^2.24.0",
"mustache": "3.0.1",
"named-urls": "^1.4.0",
"react-fast-compare": "^2.0.2"
"react-fast-compare": "^2.0.2",
"velocityjs": "1.1.3"
},
"jest": {
"setupFilesAfterEnv": [
Expand Down
1 change: 1 addition & 0 deletions app/ui-react/packages/utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './autoformHelpers';
export * from './dateHelpers';
export * from './key';
export * from './lintHelpers';
export * from './makeResolver';
export * from './WithListViewToolbarHelpers';
export * from './OptionalIntUtil';
Expand Down
11 changes: 11 additions & 0 deletions app/ui-react/packages/utils/src/lintHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as Mustache from 'mustache';
export { Mustache };

import * as Velocity from 'velocityjs';
export { Velocity };

import {
Parser as FreemarkerParser,
Tokenizer as FreemarkerTokenizer,
} from 'freemarker-parser';
export { FreemarkerParser, FreemarkerTokenizer };
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { getSteps, WithIntegrationHelpers } from '@syndesis/api';
import {
getSteps,
setActionOnStep,
TEMPLATE,
WithIntegrationHelpers,
} from '@syndesis/api';
import * as H from '@syndesis/history';
import { Integration, StringMap } from '@syndesis/models';
import { Integration, Step, StepKind, StringMap } from '@syndesis/models';
import {
IntegrationEditorLayout,
TemplateStepCard,
Expand Down Expand Up @@ -43,14 +48,22 @@ export class TemplateStepPage extends React.Component<ITemplateStepPageProps> {
{ history }
) => {
const positionAsNumber = parseInt(position, 10);
let isValid = true;
const handleUpdateLinting = (
unsortedAnnotations: any[],
annotations: any[]
) => {
isValid = annotations.length === 0;
};
const onUpdatedIntegration = async ({
action,
values,
}: StringMap<any>) => {
updatedIntegration = await (this.props.mode === 'adding'
? addStep
: updateStep)(
updatedIntegration || integration,
step,
setActionOnStep(step as Step, action, TEMPLATE) as StepKind,
flowId,
positionAsNumber,
values
Expand Down Expand Up @@ -91,11 +104,12 @@ export class TemplateStepPage extends React.Component<ITemplateStepPageProps> {
initialLanguage={language as TemplateType}
initialText={template}
onUpdatedIntegration={onUpdatedIntegration}
onUpdateLinting={handleUpdateLinting}
>
{({ controls, submitForm }) => (
<TemplateStepCard
i18nDone={'Done'}
isValid={true}
isValid={isValid}
submitForm={submitForm}
>
{controls}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import { StringMap } from '@syndesis/models';
import { DataShapeKinds } from '@syndesis/api';
import { Action, ActionDescriptor, StringMap } from '@syndesis/models';
import {
ITextEditor,
TemplateStepTemplateEditor,
TemplateStepTypeSelector,
TemplateType,
TextEditor,
} from '@syndesis/ui';
import { key } from '@syndesis/utils';
import * as React from 'react';
import {
FreemarkerModeLint,
MustacheModeLint,
TemplateSymbol,
VelocityLint,
} from './codemirror';
import { AbstractLanguageLint } from './codemirror/abstract-language-lint';

export interface IWithTemplaterChildrenProps {
controls: JSX.Element;
Expand All @@ -14,6 +23,11 @@ export interface IWithTemplaterChildrenProps {
export interface IWithTemplaterProps {
initialLanguage: TemplateType;
initialText: string;
onUpdateLinting: (
unsortedAnnotations: any[],
annotations: any[],
editor: ITextEditor
) => void;
onUpdatedIntegration(props: StringMap<any>): Promise<void>;
children(props: IWithTemplaterChildrenProps): any;
}
Expand All @@ -23,28 +37,88 @@ export interface IWithTemplaterState {
text: string;
}

const linters = {
[TemplateType.Freemarker]: new FreemarkerModeLint(),
[TemplateType.Mustache]: new MustacheModeLint(),
[TemplateType.Velocity]: new VelocityLint(),
};

const outputShapeSpecification = createSpecification([
new TemplateSymbol('message', 'string'),
]);

function extractTemplateSymbols(
templateContent: string,
parseFunction: (content: string) => TemplateSymbol[]
): TemplateSymbol[] {
let symbols: TemplateSymbol[] = [];
if (!templateContent) {
return symbols;
}
symbols = parseFunction(templateContent);
return symbols;
}

function createSpecification(symbols: TemplateSymbol[]): string {
const spec: any = {
$schema: 'http://json-schema.org/schema#',
title: 'Template JSON Schema',
type: 'object',
};
if (symbols.length === 0) {
return spec;
}
const properties: any = {};
for (const symbol of symbols) {
properties[symbol.getId()] = {
description: 'Identifier for the symbol ' + symbol.getId(),
type: symbol.getType(),
};
}
spec.properties = properties;
return JSON.stringify(spec);
}

export class WithTemplater extends React.Component<
IWithTemplaterProps,
IWithTemplaterState
> {
private linter: AbstractLanguageLint;
private editor: ITextEditor | undefined;
private action: Action | undefined;
constructor(props: IWithTemplaterProps) {
super(props);
this.state = {
language: this.props.initialLanguage,
text: this.props.initialText,
};
this.linter = linters[this.props.initialLanguage];
this.handleTemplateTypeChange = this.handleTemplateTypeChange.bind(this);
this.handleEditorChange = this.handleEditorChange.bind(this);
this.handleEditorDidMount = this.handleEditorDidMount.bind(this);
}
public handleEditorDidMount(editor: ITextEditor) {
this.editor = editor;
this.doLint();
this.buildAction(this.state.text);
}
public handleTemplateTypeChange(newType: TemplateType) {
this.linter = linters[newType];
if (typeof this.editor !== 'undefined') {
this.editor.setOption('mode', this.linter.name());
this.doLint();
}
this.buildAction(this.state.text);
this.setState({ language: newType });
}
public handleEditorChange(editor: TextEditor, data: any, text: string) {
public handleEditorChange(editor: ITextEditor, data: any, text: string) {
this.buildAction(this.state.text);
this.setState({ text });
}
public render() {
const submitForm = () => {
this.props.onUpdatedIntegration({
action: this.action,
values: {
language: this.state.language,
template: this.state.text,
Expand Down Expand Up @@ -73,13 +147,44 @@ export class WithTemplater extends React.Component<
}
initialValue={this.state.text}
onChange={this.handleEditorChange}
onUpdateLinting={this.props.onUpdateLinting}
editorDidMount={this.handleEditorDidMount}
/>
</>
);

return this.props.children({
controls,
submitForm,
});
}
private doLint() {
if (this.editor) {
(this.editor as any).performLint();
}
}
private buildAction(text: string) {
try {
const symbols = extractTemplateSymbols(text, this.linter.parse);
const inputShapeSpecification = createSpecification(symbols);
this.action = {
actionType: 'step',
descriptor: {
inputDataShape: {
kind: DataShapeKinds.JSON_SCHEMA,
name: 'Template JSON Schema',
specification: inputShapeSpecification,
} as any /* todo: type hack */,
outputDataShape: {
kind: DataShapeKinds.JSON_SCHEMA,
name: 'Template JSON Schema',
specification: outputShapeSpecification,
} as any /* todo: type hack */,
} as ActionDescriptor,
id: key(),
name: 'Templater',
};
} catch (err) {
// ignore
}
}
}
Loading

0 comments on commit 52d4742

Please sign in to comment.