From d9e1a76f2462e152f8435f62c69310ef6f890521 Mon Sep 17 00:00:00 2001 From: Egor Smirnov Date: Tue, 6 Jun 2023 12:37:59 +0300 Subject: [PATCH] add strict null checks --- src/__tests__/runBatch.ts | 1 + .../specimens/byType/CallExpressionModule.php | 2 +- .../components/BasicComponentWithProps.tsx | 5 ++- .../BasicComponentWithProps.php | 6 ++- .../ComponentBinaryOperators.php | 14 ++++--- .../InheritedProps/InheritedProps.php | 14 ++++--- .../specimens/fixes/CheckBox/CheckBox.php | 4 +- .../fixes/ExcessiveEscaping/TextContainer.php | 2 +- src/__tests__/watcherTestEnv.ts | 1 + src/ts2php/components/cli/transpile.ts | 1 + .../codegen/defaultCompilerOptions.ts | 1 + .../components/typeInference/basicTypes.ts | 38 +++++++++++++------ 12 files changed, 59 insertions(+), 30 deletions(-) diff --git a/src/__tests__/runBatch.ts b/src/__tests__/runBatch.ts index f88c2f23..d4166076 100644 --- a/src/__tests__/runBatch.ts +++ b/src/__tests__/runBatch.ts @@ -28,6 +28,7 @@ const replacedImports: CliOptions['replaceImports'] = { }; const compilerOptions = { baseUrl: baseDir, + strictNullChecks: true, paths: { '#specimens/*': ['specimens/'], }, diff --git a/src/__tests__/specimens/byType/CallExpressionModule.php b/src/__tests__/specimens/byType/CallExpressionModule.php index 0f3a50ec..5a0d7026 100644 --- a/src/__tests__/specimens/byType/CallExpressionModule.php +++ b/src/__tests__/specimens/byType/CallExpressionModule.php @@ -17,7 +17,7 @@ public static function getInstance(): CallExpressionModule { } /** - * @var mixed[] $c + * @var mixed|mixed[] $c */ public $c; diff --git a/src/__tests__/specimens/components/BasicComponentWithProps.tsx b/src/__tests__/specimens/components/BasicComponentWithProps.tsx index 40b5d197..0e1d66e9 100644 --- a/src/__tests__/specimens/components/BasicComponentWithProps.tsx +++ b/src/__tests__/specimens/components/BasicComponentWithProps.tsx @@ -4,6 +4,7 @@ const {useState} = React; interface Props { count: number; + actionText?: string; handleScroll: () => void; } @@ -12,14 +13,14 @@ interface Props { * @param props * @constructor */ -export function BasicComponentWithProps({count: cnt, handleScroll}: Props) { +export function BasicComponentWithProps({count: cnt, actionText, handleScroll}: Props) { const [count, setCount] = useState(cnt); return (

You clicked {count} times starting at {cnt}.

); diff --git a/src/__tests__/specimens/components/BasicComponentWithProps/BasicComponentWithProps.php b/src/__tests__/specimens/components/BasicComponentWithProps/BasicComponentWithProps.php index ce1b1f1b..1a56a832 100644 --- a/src/__tests__/specimens/components/BasicComponentWithProps/BasicComponentWithProps.php +++ b/src/__tests__/specimens/components/BasicComponentWithProps/BasicComponentWithProps.php @@ -26,6 +26,7 @@ private function __construct() { */ public function render(array $anon_deref_1, array $children) { $cnt = (float) $anon_deref_1["count"]; + $action_text = $anon_deref_1["actionText"]; $anon_2c03773 = [$cnt]; $count = (float) $anon_2c03773[0]; return \VK\Elephize\Builtins\IntrinsicElement::get("div")->render( @@ -35,7 +36,10 @@ public function render(array $anon_deref_1, array $children) { [], ["You clicked ", $count, " times starting at ", $cnt, "."] ), - \VK\Elephize\Builtins\IntrinsicElement::get("button")->render([], [" Click me "]), + \VK\Elephize\Builtins\IntrinsicElement::get("button")->render( + [], + [\VK\Elephize\Builtins\IntrinsicElement::escape($action_text ?: "Click me")] + ), ] ); } diff --git a/src/__tests__/specimens/components/ComponentBinaryOperators/ComponentBinaryOperators.php b/src/__tests__/specimens/components/ComponentBinaryOperators/ComponentBinaryOperators.php index 75b7d5ec..107bd2c8 100644 --- a/src/__tests__/specimens/components/ComponentBinaryOperators/ComponentBinaryOperators.php +++ b/src/__tests__/specimens/components/ComponentBinaryOperators/ComponentBinaryOperators.php @@ -35,12 +35,14 @@ public function render(array $props, array $children) { ["You clicked ", $props["count"], " times"] ), \VK\Elephize\Builtins\IntrinsicElement::get("button")->render([], [" Click me "]), - $input_value - ? \VK\Elephize\Builtins\IntrinsicElement::get("span")->render( - ["className" => "someSpan"], - [\VK\Elephize\Builtins\IntrinsicElement::escape($input_value)] - ) - : $input_value, + \VK\Elephize\Builtins\IntrinsicElement::escape( + $input_value + ? \VK\Elephize\Builtins\IntrinsicElement::get("span")->render( + ["className" => "someSpan"], + [\VK\Elephize\Builtins\IntrinsicElement::escape($input_value)] + ) + : $input_value + ), ] ); } diff --git a/src/__tests__/specimens/components/InheritedProps/InheritedProps.php b/src/__tests__/specimens/components/InheritedProps/InheritedProps.php index f27fc1de..17e12097 100644 --- a/src/__tests__/specimens/components/InheritedProps/InheritedProps.php +++ b/src/__tests__/specimens/components/InheritedProps/InheritedProps.php @@ -44,12 +44,14 @@ public function render(array $input_props, array $children) { [] ), \VK\Elephize\Builtins\IntrinsicElement::get("span")->render(["className" => "Radio__control"], []), - $children - ? \VK\Elephize\Builtins\IntrinsicElement::get("span")->render( - ["className" => "Radio__text"], - [\VK\Elephize\Builtins\IntrinsicElement::escape($children)] - ) - : $children, + \VK\Elephize\Builtins\IntrinsicElement::escape( + $children + ? \VK\Elephize\Builtins\IntrinsicElement::get("span")->render( + ["className" => "Radio__text"], + [\VK\Elephize\Builtins\IntrinsicElement::escape($children)] + ) + : $children + ), ] ); } diff --git a/src/__tests__/specimens/fixes/CheckBox/CheckBox.php b/src/__tests__/specimens/fixes/CheckBox/CheckBox.php index df28f71d..4ec0b35c 100644 --- a/src/__tests__/specimens/fixes/CheckBox/CheckBox.php +++ b/src/__tests__/specimens/fixes/CheckBox/CheckBox.php @@ -26,7 +26,7 @@ private function __construct() { */ public function render(array $props, array $children) { $checked = $props["checked"]; - $disabled = (bool) $props["disabled"]; + $disabled = $props["disabled"]; $name = $props["name"]; $id = $props["id"]; $native_props = Stdlib::objectOmit($props, ["children", "checked", "disabled", "indeterminate", "name", "id"]); @@ -44,7 +44,7 @@ public function render(array $props, array $children) { "type" => "checkbox", "checked" => !!$checked, "name" => \VK\Elephize\Builtins\IntrinsicElement::escape((string) $name), - "disabled" => $disabled, + "disabled" => \VK\Elephize\Builtins\IntrinsicElement::escape($disabled), ]), [] ), diff --git a/src/__tests__/specimens/fixes/ExcessiveEscaping/TextContainer.php b/src/__tests__/specimens/fixes/ExcessiveEscaping/TextContainer.php index 6c6cef3e..9cc89091 100644 --- a/src/__tests__/specimens/fixes/ExcessiveEscaping/TextContainer.php +++ b/src/__tests__/specimens/fixes/ExcessiveEscaping/TextContainer.php @@ -25,7 +25,7 @@ private function __construct() { * @return ?string */ public function render(array $anon_deref_1, array $children) { - $align = (string) $anon_deref_1["align"]; + $align = $anon_deref_1["align"]; $class_names = ["content-text--container"]; if ($align) { array_push($class_names, "content-text--container__align-" . $align); diff --git a/src/__tests__/watcherTestEnv.ts b/src/__tests__/watcherTestEnv.ts index 5308fbb0..22cdc34e 100644 --- a/src/__tests__/watcherTestEnv.ts +++ b/src/__tests__/watcherTestEnv.ts @@ -26,6 +26,7 @@ const ignoreImportRules: Set = new Set(); const replaceImportRules = { }; const compilerOptions = { baseUrl: baseDir, + strictNullChecks: true, paths: { '#specimens/*': ['specimens'], }, diff --git a/src/ts2php/components/cli/transpile.ts b/src/ts2php/components/cli/transpile.ts index 2a498ebe..8e526813 100644 --- a/src/ts2php/components/cli/transpile.ts +++ b/src/ts2php/components/cli/transpile.ts @@ -64,6 +64,7 @@ export function transpile(options: CliOptions, baseDir: string, outDir: string, const compilerOptions = { baseUrl: baseDir, paths: options.tsPaths || {}, + strictNullChecks: true, }; (options.watch ? translateCodeAndWatch : translateCode)( diff --git a/src/ts2php/components/codegen/defaultCompilerOptions.ts b/src/ts2php/components/codegen/defaultCompilerOptions.ts index 5748978c..07e3e6b4 100644 --- a/src/ts2php/components/codegen/defaultCompilerOptions.ts +++ b/src/ts2php/components/codegen/defaultCompilerOptions.ts @@ -18,4 +18,5 @@ export const defaultCompilerOptions: ts.CompilerOptions = { allowUnreachableCode: true, allowJs: true, resolveJsonModule: true, + strictNullChecks: true, }; diff --git a/src/ts2php/components/typeInference/basicTypes.ts b/src/ts2php/components/typeInference/basicTypes.ts index a6f10dde..7dc8e103 100644 --- a/src/ts2php/components/typeInference/basicTypes.ts +++ b/src/ts2php/components/typeInference/basicTypes.ts @@ -257,7 +257,7 @@ const transformTypeName = (type: ts.Type, node: ts.Node, checker: ts.TypeChecker function describeNodeType(node: ts.Node | undefined, type: ts.Type, checker: ts.TypeChecker, log: LogObj) { const nodeIdentForLog = node?.getText(); - const optionalMark = (node?.parent.kind === ts.SyntaxKind.Parameter && ( + let optionalMark = (node?.parent.kind === ts.SyntaxKind.Parameter && ( (node.parent as ts.ParameterDeclaration).initializer || (node.parent as ts.ParameterDeclaration).questionToken )) ? '?' : ''; @@ -274,16 +274,32 @@ function describeNodeType(node: ts.Node | undefined, type: ts.Type, checker: ts. const customTypehints = checkCustomTypehints(type, checker); if (customTypehints) { - const types = customTypehints.foundTypes.map((t) => { - if (typeof t === 'string') { - return optionalMark + t; - } - // Some of union members may be literal types - return optionalMark + describeAsApparentType(t, node!, checker, log, nodeIdentForLog); - }).filter((t) => !customTypehints.typesToDrop.includes(t)); - const typehint = Array.from(new Set(([]) - .concat(types))) - .join('|'); + if (!optionalMark) { + optionalMark = customTypehints.foundTypes.find( + (t) => typeof t !== 'string' && t.getFlags() & ts.TypeFlags.Undefined + ) + ? '?' + : ''; + } + + const types = customTypehints.foundTypes + .filter( + (t) => typeof t === 'string' || !(t.getFlags() & ts.TypeFlags.Undefined) + ) + .map((t) => { + if (typeof t === 'string') { + return optionalMark + t; + } + // Some of union members may be literal types + return ( + optionalMark + + describeAsApparentType(t, node!, checker, log, nodeIdentForLog) + ); + }) + .filter((t) => !customTypehints.typesToDrop.includes(t)); + const typehint = Array.from(new Set(([]).concat(types))).join( + '|' + ); log.typehint('Inferred type of node: %s -> %s [1]', [nodeIdentForLog || '', typehint]); return typehint; }