TypeScript checks without TypeScript

Boston TypeScript Meetup

February 2019

these slides

C / C++ / C# / Java / CoffeeScript / JavaScript / Node / Angular / Vue / Cycle.js / functional / testing

18 people. Atlanta, Philly, Boston, Chicago, NYC

Fast, easy and reliable testing for anything that runs in a browser

I have Cypress.io stickers

Presentation

  1. TypeScript = love & hate story
  2. JSDoc types
  3. Types for IntelliSense (Cypress.io example)
    • Extending global types
    • Extending window interface

C, C++

Java, C#

CoffeeScript

JavaScript

Angular, Node

Node, Hyperapp, Vue

C, C++

Java, C#

CoffeeScript

JavaScript

Angular, Node

Node, Hyperapp, Vue

Templates

template <class T>
 struct is_class_or_union
 {
 // SFINAE eliminates this when the type of arg is invalid
 template <class U>
 static yes tester(int U::*arg);
 // overload resolution prefers anything at all over "..."
 template <class U>
 static no tester(...);
 // see which overload is chosen when U == T
 static bool const value
 = sizeof(tester<T>(0)) == sizeof(yes);
 typedef mpl::bool_<value> type;
 };
 struct X{};
 BOOST_STATIC_ASSERT(is_class_or_union<X>::value);
 BOOST_STATIC_ASSERT(!is_class_or_union<int>::value);
C:\PROGRA~1\MICROS~4\VC98\INCLUDE\xutility(19) : error C2679:
 binary '=''std::string' (or there is no acceptable conversion)
 foo.cpp(9) : see reference to function template instantiation
 'class std::back_insert_iterator<class std::map<std::string,
 std::string,struct std::less<std::string>,class
 std::allocator<std::string> > > __cdecl std::copy(class
 std::list<std::string,class std::allocator<std::string>
 >::iterator,class std::list<std::string,class std::allocator<
 std::string> >::iterator,class std::back_insert_iterator<
 class std::map<std::string,std::string,struct std::
 less<std::string>,class std::allocator<std::string> > >)'
 being compiled

What am I doing ...

C++ / Java / C#

CoffeeScript

JavaScript

JavaScript around us

  • Browser

  • Server

  • Desktop

  • Mobile

  • Gray areas:

    • IoT, robots, code snippets, build scripts, sw

NPM Registry

aka OSS heaven

Stick Express into ServiceWorker?

self.addEventListener('fetch', function (event) {
  const parsedUrl = url.parse(event.request.url)
  var req = {
    url: parsedUrl.href,
    method: event.request.method,
    body: body,
    headers: {
      'content-type': event.request.headers.get('content-type')
    },
    unpipe: function () {}
  }
  var res = { /* our mock Node http Server Response object */ }
  event.respondWith(new Promise(function (resolve) {
    app(req, res)
  }))
})

whatever works

Hack Node require?

// really-need index.js
var _compileStr = Module.prototype._compile.toString();
// pass all arguments from loaded module to our self.require
_compileStr = _compileStr.replace('self.require(path);',
  'self.require.apply(self, arguments);'); // 1
/* jshint -W061 */
var patchedCompile = eval('(' + _compileStr + ')'); // 2
Module.prototype._compile = function(content, filename) {
  return patchedCompile.call(this, content, filename);
};

sure, replace a string in Node's internals

How to become a better hacker

How to write innovative, unusual and simply cool software.

Try to connect separate pieces of software together ... This is also why I don't use type system like Typescript - it does not let me hack things together!

Jan 2016

undefined is not a function

E2E

We will need a lot of tests

unit

E2E

unit

Replacing some tests

linters

Ramda, _

crash

reporting

E2E

unit

Linters gonna lint

linters

Ramda, _

crash

reporting

✅ ESLint / Standard

😡 CoffeeLint

Ok, ok. It would be useful to have the computer check this code statically ...

2017: Flow Vs TypeScript

MS

VSCode is nice

Facebook

worst product & company

2017: Flow Vs TypeScript

Anders Hejlsberg

TypeScript

C#

Turbo Pascal

Facebook

worst product & company

2017: Flow Vs TypeScript

Anders Hejlsberg

TypeScript

C#

Turbo Pascal

Facebook

worst product & company

winner 🏆

Flow: Comment Types

// @flow

/*::
type MyAlias = {
  foo: number,
  bar: boolean,
  baz: string,
};
*/

function method(value /*: MyAlias */) /*: boolean */ {
  return value.bar;
}

method({ foo: 1, bar: true, baz: ["oops"] });
  1. No changes to the source code, still JS
  2. Just run addition CLI tool
  3. Profit

Benefits

On the other hand ...

  1. Everything about Flow IF you don't work at Facebook 👎

So I started ❤️ TS

  1. VSCode support
  2. npm i -D @types/X

Still 🤢 of tooling

Even with TSC and Parcel ...

JSDoc + JS =  type checks 😍

Aug 2018

JSDoc + JS =  type checks 😍

Step 1: code in JS

const add = (a, b) => a + b
add(2, 'foo')

Step 2: add comment

Profit: IntelliSense

Step 3: @ts-check

Step 3: @ts-check

Profit: tsc check

$ npx tsc --noEmit --allowJs app.js 
app.js:9:8 - error TS2345: Argument of type '"foo"' is not assignable 
                           to parameter of type 'number'.

9 add(2, 'foo')
         ~~~~~


Found 1 error.

How far can we push types in JSDoc?

Answer: pretty far

define object types

cast types

⚠️ VSCode finds "Person" type, but "tsc" does not

import definitions from another file

Tip: restarting TypeScript server changes results

Write type definitions in ".d.ts" files and use from JS

Use TS types from JavaScript

✅ JSDoc types

✅ .d.ts + JavaScript

Why it matters:

Cypress End-to-End test runner

$ npm install -D cypress
it('adds 2 todos', () => {
  cy.visit('http://localhost:3000')
  cy.get('.new-todo')
    .type('learn testing{enter}')
    .type('be cool{enter}')
  cy.get('.todo-list li')
    .should('have.length', 2)
})

Cypress API is huge

https://on.cypress.io/<command>

  1. read docs for hours
  2. go through the tutorial*
  3. read more docs
  4. write tests

New Testing Tool

  1. read "Hello World"
  2. copy to your test
  3. write useful tests
  4. gradually improve

New Testing Tool

Cypress Types

types/
  index.d.ts

package.json
  "types": "types"

Explains global "cy" object

User test - just add this comment

Explains "cy.visit" command

Completion for BDD assertions

Completion for specific assertion with examples

IntelliSense-driven development

every command and assertion

links to full docs

inline examples

Tip: Examples in @example tag

IntelliSense brakes on "@" character

Tip: Examples in @example tag

IntelliSense does not respect indent

Use Markdown code block

✅ Fixes "@" and indent

👎 tslint freaks out

pull request #3163

What if users extend global object "cy"?

The test is passing

"@ts-check" complaints

1. Merge new method with existing global TypeScript definition in ".d.ts" file

2. Use "reference path=" comment to load ".d.ts" file

What if app adds to "window"?

app.jsx

The test is passing

1. Add property to interface "Window"

2. Use "reference path=" comment to load ".d.ts" file

Bonus: JSON IntelliSense

VSCode: in the user settings, global or workspace set:

{
  "json.schemas": [
    {
      "fileMatch": ["cypress.json"],
      "url": "https://on.cypress.io/cypress.schema.json"
    }
  ]
}

VSCode alternative: add "$schema" property to your JSON file

{
  "viewportWidth": 600,
  "viewportHeight": 800,
  "ignoreTestFiles": "answer.js",
  "baseUrl": "http://localhost:3000",
  "$schema": "https://on.cypress.io/cypress.schema.json"
}

JS + JSDoc type-checking works

Conclusions

Conclusions

JS + TS type-checking works

Distribute good types + JSDoc with your library or tool

Conclusions

Thank you

Boston TypeScript Meetup

February 2019

TypeScript checks without TypeScript

By Gleb Bahmutov

TypeScript checks without TypeScript

In this presentation, I will show how to get type checks without going all the way to TypeScript. We will look at how you can write JSDoc comments to describe types. I will also show how we provide external TypeScript definitions in Cypress end-to-end tests where the tests themselves are written in JavaScript.

  • 1,047
Loading comments...

More from Gleb Bahmutov