diff --git a/.vscode/settings.json b/.vscode/settings.json index a3effaf..3370c8d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,5 +10,6 @@ "typescript.tsc.autoDetect": "off", "mochaExplorer.files": "**/*.test.ts", "mochaExplorer.require": "ts-node/register", - "mochaExplorer.optsFile": "mocha.opts" -} \ No newline at end of file + "mochaExplorer.optsFile": "mocha.opts", + "editor.formatOnSave": true +} diff --git a/src/generators/class-generator.ts b/src/generators/class-generator.ts index ea56881..907a945 100644 --- a/src/generators/class-generator.ts +++ b/src/generators/class-generator.ts @@ -187,9 +187,9 @@ class ${rootWidget.controller}Base { return ` class ${widgetName} extends StatelessWidget${mixinsCode} { - ${rootWidget.params.filter(a => !!a.name).map(a => `final ${a.type ? a.type + ' ' : ''}${a.name}${a.value !== undefined ? ' = ' + a.value : ''};`).join('\n ')} + ${rootWidget.params.filter(a => !!a.name).map(a => `final ${a.type ? (a.value === undefined && !a.required ? a.type + '? ' : a.type + ' ') : ''}${a.name}${a.value !== undefined ? ' = ' + a.value : ''};`).join('\n ')} ${widgetName}(${rootWidget.params.length ? '{': ''} - ${rootWidget.params.map(a => `${a.required ? '@required ' : ''}${a.name ? `this.${a.name}` : `${(a.type ? a.type + ' ' : '')}${a.superParamName}`}`).join(',\n ')} + ${rootWidget.params.map(a => `${a.required ? 'required ' : ''}${a.name ? `this.${a.name}` : `${(a.type ? a.type + ' ' : '')}${a.superParamName}`}`).join(',\n ')} ${rootWidget.params.length ? '}': ''}); ${buildMethodContent} } @@ -198,18 +198,23 @@ class ${widgetName} extends StatelessWidget${mixinsCode} { private createStatefulWidget(widgetName: string, mixinsCode: string, rootWidget: RootWidgetModel, controllers: VariableModel[], routeAware: boolean, routeAwareStateMethods: string, buildMethodContent: string, hasController: boolean, formControls: FormControlModel[]) { const stateVarsDeclaration: string[] = [ - ...(hasController ? [`${rootWidget.controller} ctrl;`] : []), + ...(hasController ? [`late ${rootWidget.controller} ctrl;`] : []), ...controllers.filter(a => !a.skipGenerate).map(a => a.isPrivate ? `final ${a.name} = new ${a.type}();` : `${a.type} ${a.name};`), ...rootWidget.providers.map(a => `${a.type} ${a.name};`), - ...rootWidget.vars.map(a => `${a.type} ${a.name};`), - ...(routeAware ? [`RouteObserver _routeObserver;`] : []) + ...rootWidget.vars.map(a => `late ${a.type} ${a.name};`), + ...(routeAware ? [`late RouteObserver _routeObserver;`] : []) ]; const stateVarsInit: string[] = [ ...(hasController ? [`ctrl = new ${rootWidget.controller}();`] : []), ...(hasController ? rootWidget.params.filter(a => !!a.name).map(a => `ctrl._${a.name} = widget.${a.name};`) : []), ...controllers.filter(a => !a.isPrivate && !a.skipGenerate).map(a => `${hasController ? `ctrl._${a.name} = `: ''}${a.name} = ${a.value ? a.value : `new ${a.type}()`};`), ...rootWidget.vars.map(a => `${hasController ? `ctrl._${a.name} = `: ''}${a.name} = ${a.value};`), - ...(hasController ? [`WidgetsBinding.instance?.addPostFrameCallback((_) => mounted ? ctrl.afterFirstBuild(context) : null);`] : []) + ...(hasController ? [`WidgetsBinding.instance.addPostFrameCallback((_) => mounted ? ctrl.afterFirstBuild(context) : null);`] : []) + ]; + const stateVarsUpdate: string[] = [ + ...(hasController ? rootWidget.params.filter(a => !!a.name).map(a => `ctrl._${a.name} = widget.${a.name};`) : []), + ...controllers.filter(a => !a.isPrivate && !a.skipGenerate).map(a => `${hasController ? `ctrl._${a.name} = `: ''}${a.name} = ${a.value ? a.value : `new ${a.type}()`};`), + ...rootWidget.vars.map(a => `${hasController ? `ctrl._${a.name} = `: ''}${a.name} = ${a.value};`), ]; const superParams = rootWidget.params .filter(a => a.superParamName) @@ -220,9 +225,9 @@ class ${widgetName} extends StatelessWidget${mixinsCode} { return ` class ${widgetName} extends StatefulWidget { - ${rootWidget.params.filter(a => !!a.name).map(a => `final ${a.type ? a.type + ' ' : ''}${a.name};`).join('\n ')} + ${rootWidget.params.filter(a => !!a.name).map(a => `final ${a.type ? (a.value === undefined && !a.required ? a.type + '? ' : a.type + ' ') : ''}${a.name};`).join('\n ')} ${widgetName}(${rootWidget.params.length ? '{': ''} - ${rootWidget.params.map(a => `${a.required ? '@required ' : ''}${a.name ? `this.${a.name}` : `${(a.type ? a.type + ' ' : '')}${a.superParamName}`}${a.value !== undefined ? ' = ' + a.value : ''}`).join(',\n ')} + ${rootWidget.params.map(a => `${a.required ? 'required ' : ''}${a.name ? `this.${a.name}` : `${(a.type ? a.type + ' ' : '')}${a.superParamName}`}${a.value !== undefined ? ' = ' + a.value : ''}`).join(',\n ')} ${rootWidget.params.length ? '}': ''})${superCtor}; @override @@ -238,9 +243,14 @@ class _${widgetName}State extends State<${widgetName}>${mixinsCode} { super.initState();${(stateVarsInit.length > 0 ? '\n ' : '') + stateVarsInit.join(`\n `)} } + @override + void didUpdateWidget(${widgetName} oldWidget) { + super.didUpdateWidget(oldWidget);${(stateVarsUpdate.length > 0 ? '\n ' : '') + stateVarsUpdate.join(`\n `)} + } + @override void didChangeDependencies() { - super.didChangeDependencies();${routeAware ? `\n _routeObserver = Provider.of>(context)..subscribe(this, ModalRoute.of(context));` : '' + super.didChangeDependencies();${routeAware ? `\n _routeObserver = Provider.of>(context)..subscribe(this, ModalRoute.of(context) as Route);` : '' }${(rootWidget.providers.length ? '\n ' : '') + rootWidget.providers.map(a => `${hasController ? `ctrl._${a.name} = `: ''}${a.name} = Provider.of<${a.type}>(context);`).join('\n ') }${hasController ? `\n ctrl._load(context);` : ''} } @@ -257,7 +267,9 @@ class _${widgetName}State extends State<${widgetName}>${mixinsCode} { private createControllerVar(a: VariableModel): string { a.type = a.type || 'var'; - return `${a.type} _${a.name};\n ${a.type} get ${a.name} => _${a.name};`; + return ((a as any).required || a.value !== undefined) ? + `late ${a.type} _${a.name};\n ${a.type} get ${a.name} => _${a.name};` : + `${a.type}? _${a.name};\n ${a.type}? get ${a.name} => _${a.name};`; } generateControllerFile(fileName: string, rootWidget: RootWidgetModel): string { diff --git a/src/language-features/providers/fix_code_action_provider.ts b/src/language-features/providers/fix_code_action_provider.ts index 0b2be7b..0a019b9 100644 --- a/src/language-features/providers/fix_code_action_provider.ts +++ b/src/language-features/providers/fix_code_action_provider.ts @@ -1,3 +1,5 @@ +import * as vscode from "vscode"; + import { CancellationToken, CodeAction, @@ -12,10 +14,10 @@ import { WorkspaceEdit, commands, } from 'vscode'; -import { Location, SourceChange } from '../dart-ext-types'; +import { getDartCodeIndex, getDartDocument } from '../utils'; import { RankedCodeActionProvider } from './ranking_code_action_provider'; -import { getDartDocument } from '../utils'; +import { SourceChange } from '../dart-ext-types'; export class FixCodeActionProvider implements RankedCodeActionProvider { constructor(public readonly selector: DocumentSelector) { } @@ -26,10 +28,13 @@ export class FixCodeActionProvider implements RankedCodeActionProvider { providedCodeActionKinds: [CodeActionKind.QuickFix], }; - public async provideCodeActions(xmlDocument: TextDocument, xmlRange: Range, context: CodeActionContext, token: CancellationToken): Promise { - const dartDocument = await getDartDocument(xmlDocument); - const location = ((context.diagnostics[0] || {}) as any).location as Location; - if (!location || location.offset === -1) { + public async provideCodeActions(xmlDocument: TextDocument, cursorRange: Range, context: CodeActionContext, token: CancellationToken): Promise { + if (context.diagnostics.length < 1) { + return undefined; + } + + const xmlRange = context.diagnostics[0].range; + if (!xmlRange || xmlRange.start == null) { return undefined; } @@ -38,8 +43,14 @@ export class FixCodeActionProvider implements RankedCodeActionProvider { } try { - const rangeStart = dartDocument.positionAt(location.offset); - let results: (Command | CodeAction)[] = await commands.executeCommand('vscode.executeCodeActionProvider', dartDocument.uri, new Range(rangeStart, rangeStart.translate({ characterDelta: 10 }))); + const dartDocument = await getDartDocument(xmlDocument); + const dartOffset = getDartCodeIndex(xmlDocument, xmlRange.start, dartDocument, xmlRange); + const rangeEnd = dartOffset != -1 ? dartDocument.positionAt(dartOffset) : null; + + const dartRange = dartOffset != -1 ? new Range(rangeEnd.translate({ characterDelta: -(xmlRange.end.character - xmlRange.start.character) }), rangeEnd) : + new Range(dartDocument.positionAt(0), dartDocument.positionAt(dartDocument.getText().length - 1)) ; + + let results: (Command | CodeAction)[] = await commands.executeCommand('vscode.executeCodeActionProvider', dartDocument.uri, dartRange ); return results.map(a => this.buildCodeAction(xmlDocument, a)); } catch (e) { @@ -50,7 +61,7 @@ export class FixCodeActionProvider implements RankedCodeActionProvider { private buildCodeAction(document: TextDocument, command: Command | CodeAction): CodeAction { const innerCommand = command.command as any; - const title = innerCommand && innerCommand.title ? innerCommand.title : command.title; + const title = command.title ? command.title : innerCommand.title; if (!title || !title.startsWith('Import library')) { return null; } diff --git a/src/language-features/utils.ts b/src/language-features/utils.ts index 65b98e1..abdd875 100644 --- a/src/language-features/utils.ts +++ b/src/language-features/utils.ts @@ -1,6 +1,8 @@ -import { TextDocument, workspace, Uri, Position, Range } from "vscode"; import * as path from "path"; -import { isAttribute, getXPath, isTagName, isClosingTagName } from "./xmlUtils"; + +import { Position, Range, TextDocument, Uri, workspace } from "vscode"; +import { getXPath, isAttribute, isClosingTagName, isTagName } from "./xmlUtils"; + import { Location } from "./dart-ext-types"; export const isWin = /^win/.test(process.platform); @@ -159,6 +161,11 @@ export function getDartCodeIndex(xmlDocument: TextDocument, xmlPosition: Positio dartOffset = classIndex; } } + } else { + if (wordRange) { + const code = xmlDocument.getText(wordRange); + dartOffset = dart.indexOf(code); + } } return dartOffset; @@ -220,4 +227,4 @@ export function getXmlCodeWordLocation(xmlDocument: TextDocument, dart: string, const startPos = xmlDocument.positionAt(xmlOffset + 1); return new Range(startPos, startPos.translate(0, word.length)); -} \ No newline at end of file +} diff --git a/src/manager.ts b/src/manager.ts index b583b92..173e341 100644 --- a/src/manager.ts +++ b/src/manager.ts @@ -1,25 +1,27 @@ +import * as fs from 'fs'; +import * as mkdirp from 'mkdirp'; import * as path from "path"; import * as vscode from "vscode"; -import * as mkdirp from 'mkdirp'; -import * as fs from 'fs'; -import { denodeify } from 'q'; -import { WidgetResolver } from "./resolvers/widget-resolver"; -import { ParseXml } from "./parser/parser"; -import { PropertyHandlerProvider } from "./providers/property-handler-provider"; -import { WrapperPropertyHandler } from "./property-handlers/wrapper-property"; + +import { Config, ConfigValueTransformer } from "./models/config"; +import { IValueTransformer, ValueTransformersProvider } from "./providers/value-transformers-provider"; +import { registerBuiltInPropertyHandlers, registerBuiltInValueTransformers } from "./builtin-handlers"; + import { ChildWrapperPropertyHandler } from "./property-handlers/child-wrapper-property"; -import { ValueTransformersProvider, IValueTransformer } from "./providers/value-transformers-provider"; -import { EnumValueTransformer } from "./value-transformers/enum"; -import { EdgeInsetsValueTransformer } from "./value-transformers/edge-insets"; +import { ClassCodeGenerator } from "./generators/class-generator"; import { ColorValueTransformer } from "./value-transformers/color"; +import { EdgeInsetsValueTransformer } from "./value-transformers/edge-insets"; +import { EnumValueTransformer } from "./value-transformers/enum"; import { LocalizationGenerator } from "./generators/localization-generator"; -import { ClassCodeGenerator } from "./generators/class-generator"; +import { ParseXml } from "./parser/parser"; +import { PipeValueResolver } from "./resolvers/pipe-value-resolver"; +import { PropertyHandlerProvider } from "./providers/property-handler-provider"; +import { PropertyResolver } from "./resolvers/property-resolver"; import { WidgetCodeGenerator } from "./generators/widget-generator"; +import { WidgetResolver } from "./resolvers/widget-resolver"; +import { WrapperPropertyHandler } from "./property-handlers/wrapper-property"; +import { denodeify } from 'q'; import { insertAutoCloseTag } from "./autoclose/autoclose"; -import { Config, ConfigValueTransformer } from "./models/config"; -import { registerBuiltInValueTransformers, registerBuiltInPropertyHandlers } from "./builtin-handlers"; -import { PropertyResolver } from "./resolvers/property-resolver"; -import { PipeValueResolver } from "./resolvers/pipe-value-resolver"; const mkdir = denodeify(mkdirp); const writeFile = denodeify(fs.writeFile); @@ -171,11 +173,11 @@ export default class Manager { layoutDart = this.classGenerator.generate(rootWidget, controllerFileName); } catch (ex) { - const diagnostic = this.getExceptionDiagnostics(ex.message); + const diagnostic = this.getExceptionDiagnostics((ex as any).message); if (diagnostic) { this.diagnostics.set(fileUri, [diagnostic]); } - const customMessage = this.getCustomErrorMessage(ex.message); + const customMessage = this.getCustomErrorMessage((ex as any).message); if (customMessage) { vscode.window.showErrorMessage(customMessage); this.output.appendLine(customMessage); diff --git a/src/property-handlers/builder.ts b/src/property-handlers/builder.ts index 560837c..d9318c9 100644 --- a/src/property-handlers/builder.ts +++ b/src/property-handlers/builder.ts @@ -1,7 +1,9 @@ -import { CustomPropertyHandler, PropertyResolveResult, WidgetResolveResult } from "../providers/property-handler-provider"; import * as parseXml from '../parser/types'; -import { WidgetModel, ExtraDataModel, AttributeModel, PropertyModel, AttributeInfo } from '../models/models'; + +import { AttributeInfo, AttributeModel, ExtraDataModel, PropertyModel, WidgetModel } from '../models/models'; +import { CustomPropertyHandler, PropertyResolveResult, WidgetResolveResult } from "../providers/property-handler-provider"; import { extractForLoopParams, makeTabs, sortProperties, spaceAfter } from "../utils"; + import { PropertyResolver } from "../resolvers/property-resolver"; export class BuilderHandler extends CustomPropertyHandler { @@ -199,7 +201,7 @@ export class BuilderHandler extends CustomPropertyHandler { // if (hasItemList && (!data.params || data.indexName)) { if (hasItemList) { code += ` -${tabs} final ${spaceAfter(data.typeName)}${data.itemName} = ${data.listValueVariableName} == null || ${data.listValueVariableName}.length <= ${indexName} || ${data.listValueVariableName}.length == 0 ? null : ${data.listValueVariableName}[${indexName}];`; +${tabs} final ${spaceAfter(data.typeName)}${data.itemName} = (${data.listValueVariableName} as dynamic) == null || ${data.listValueVariableName}.length <= ${indexName} || ${data.listValueVariableName}.length == 0 ? [] : ${data.listValueVariableName}[${indexName}];`; } if (ifWidgets && ifWidgets.length) { diff --git a/src/property-handlers/wrapper-animation.ts b/src/property-handlers/wrapper-animation.ts index 0ef2cdc..0907c88 100644 --- a/src/property-handlers/wrapper-animation.ts +++ b/src/property-handlers/wrapper-animation.ts @@ -1,7 +1,8 @@ -import { WidgetModel, PropertyModel, VariableModel, AttributeInfo } from '../models/models'; +import { AttributeInfo, PropertyModel, VariableModel, WidgetModel } from '../models/models'; + +import { PropertyResolver } from '../resolvers/property-resolver'; import { WrapperPropertyHandler } from "./wrapper-property"; import { makeTabs } from '../utils'; -import { PropertyResolver } from '../resolvers/property-resolver'; export class WrapperAnimationHandler extends WrapperPropertyHandler { isElement = true; @@ -41,7 +42,7 @@ export class WrapperAnimationHandler extends WrapperPropertyHandler { value:`GlobalKey()` }); vars.push({ - name:`AnimationBuilderStateMixin get ${animationControllerName} => _${animationControllerName}Key.currentState;`, + name:`AnimationBuilderStateMixin get ${animationControllerName} => _${animationControllerName}Key.currentState as AnimationBuilderStateMixin;`, type: '', value:`` }); @@ -69,7 +70,7 @@ export class WrapperAnimationHandler extends WrapperPropertyHandler { extraData: { parameters: [ { name: 'animations', type: 'Map' }, - { name: `child`, type: 'Widget' } + { name: `child`, type: 'Widget?' } ], addReturn: true } @@ -124,7 +125,7 @@ export class WrapperAnimationHandler extends WrapperPropertyHandler { const values = tweens.map(t => { return { propertyName: t.property, - propertyValue: `animations["${t.property}"]${isTransitionWidget ? '' : '.value'}` + propertyValue: `animations["${t.property}"]${isTransitionWidget ? '' : '!.value'}` }; }); @@ -180,4 +181,4 @@ export class WrapperAnimationHandler extends WrapperPropertyHandler { .join(',\n'); return `${tabs}tweenMap: {\n${code}\n${tabs}}`; } -} \ No newline at end of file +} diff --git a/src/resolvers/pipe-value-resolver.ts b/src/resolvers/pipe-value-resolver.ts index 0c9589b..fd4b91b 100644 --- a/src/resolvers/pipe-value-resolver.ts +++ b/src/resolvers/pipe-value-resolver.ts @@ -1,8 +1,10 @@ -import { scan } from "../parser/parser"; -import * as parseXml from '../parser/types'; import * as Syntax from '../parser/syntax'; -import { WidgetModel, ExtraDataModel } from "../models/models"; -import { makeVariableName, makePipeUniqueName } from "../utils"; +import * as parseXml from '../parser/types'; + +import { ExtraDataModel, WidgetModel } from "../models/models"; +import { makePipeUniqueName, makeVariableName } from "../utils"; + +import { scan } from "../parser/parser"; export class PipeValueResolver { @@ -103,7 +105,7 @@ export class PipeValueResolver { extraData: { parameters: [ { name: 'context', type: 'BuildContext' }, - { name: `${snapshotVarName}`, type: '' } + { name: `${snapshotVarName}`, type: 'dynamic' } ], logic: [ ...(addLocalVar ? [`final ${resultVarName} = ${snapshotVarName}.data;`] : []), @@ -315,4 +317,4 @@ export class PipeValueResolver { const matches = scan({ xml: args, pos: 0 }, Syntax.Global.PipeArgs); return matches; } -} \ No newline at end of file +}