Detecting class instance undefined method name in javascript - javascript

I'm working in a new-to-me large javascript codebase that is a work-in-progress. As happens often in a large codebase, a variety of people have added code that has not been tested. I just spent way too much time debugging an issue that ended up being caused by a misspelled method name (essentially a missing method):
stream.renegotiate() // this is what is should have been
stream.renogotiate() // this is what it was
The project is bundled using webpack, without linting. So I thought I could just add eslint to the webpack config and it would tell me about the problems. It works for misspelled global methods, but is not checking class member name spelling.
Here is my .eslintrc file:
{
"plugins": [
"eslint-plugin-import"
],
"extends": [
"eslint:recommended",
"plugin:import/recommended",
"plugin:import/typescript"
],
"rules": {
"no-undef": "error",
"no-extra-semi": "off",
"no-unused-vars": "off"
},
"env": {
"browser": true,
"es2022": true
}
}
The typescript option is in there because a few of the newer files in the project are typescript, but still there are 100+ legacy javascript files that have been recently worked on that have issues.
Is there a way to get eslint (or some other checker) to be able to detect and error-report the example above of stream.renogotiate() without doing a fullscale migration to typescript?

Javascript is loosely typed and it's possible to monkeypatch new methods onto existing classes and objects at runtime, so (as far as I know) there's no way to catch these sorts of issues at build time without something like typescript, even if you know the object's type.
Consider the example below. The test function throws on the first invocation but not on the second, despite being passed the same stream instance both times.
class Stream {
renegotiate () {
console.log('renegotiate invoked');
}
}
function test (stream) {
try {
stream.renOgotiate();
}
catch {
console.log('error trying to invoke renOgotiate')
}
}
const stream = new Stream();
test(stream); // throws
Stream.prototype.renOgotiate = () => console.log('renOgotiate invoked');
test(stream); // doesn't throw

Turns out the typescript compiler can be used for exactly what I described above. You can use it in vscode to check individual files by adding this to the top of a .js file:
// #ts-check
If you want to check a whole project, create a jsoncfig.json file wherever the root of the javascript content is, then run the typescript compiler from the command-line with this:
npx tsc -p jsconfig.json
In my case, it did exactly what I was hoping it would do, highlighted a significant number of undefined symbols, including the one originally referenced, with this output:
error TS2339: Property 'renogotiate' does not exist on type 'MediaStream'.
This is a great first-step in porting a large javascript project to typescript. Thanks to the vscode docs for the guidance: https://code.visualstudio.com/docs/nodejs/working-with-javascript
FWIW, here is what I used for my jsconfig.json:
{
"compilerOptions": {
"module": "commonjs",
"target": "es2020",
"checkJs": true,
"allowJs": true,
"noEmit": true,
"skipLibCheck": true
},
"exclude": ["node_modules"],
"include": ["src/legacy/**/*"],
}

Related

Importing PostCSS file as module returns SyntaxError

I've been trying to import PostCSS file as module in my TypeScript, using Parcel. I've followed a YouTube video partially (not fully because they're running an older version of Parcel.), and also followed some parts of the Parcel documentation. Parcel is transpiling the PostCSS to CSS properly, and even including the style in the HTML document, but it's also copying and pasting the transpiled CSS in the JavaScript tag instead of converting it to a proper module.export object and therefore causing a SyntaxError. I am very unsure what I could possibly have done wrong. I, either can't seem to find anyone whose had a similar issue, or am just bad at wording my issue.
{
// "modules": true,
"plugins": {
"postcss-import": true,
"postcss-url": true,
"postcss-nested": true
}
}
This is my .postcssrc, it's working as far as I know. When I add the "modules": true, it tells me to remove it and add "cssModules" in package.json instead
{
// ...
"#parcel/transformer-css": {
"cssModules": {
"pattern": "av-[hash]-[local]",
"global": true
}
},
"browserslist": [
"> 0.5%, last 2 versions, not dead"
]
}
This is contents in my package.json which is relevant to parcel. And these are the dependencies that were (mostly) automatically installed by Parcel.
import * as classes from "../css/style.pcss";
console.log(classes);
This is where I try to get the exported class modules, but It doesn't even log anything because of the SyntaxError.

How to access Textencoder as a global instead of importing it from the util package?

I'm using Node v18.0.0 with TypeScript and it seems that the TextEncoder class is a global.
Unfortunately this code const textEncoder = new TextEncoder(); is invalid because it doesn't know about the class. I will have to import the util package to make it work, otherwise I get the error
Cannot find name 'TextEncoder'. Did you mean 'textEncoder'?ts(2552)
I'm using the packages "#types/node": "17.0.23" and "typescript": "4.6.3". The tsconfig.json file
{
"compilerOptions": {
"baseUrl": ".",
"declaration": true,
"esModuleInterop": true,
"lib": [ "esnext" ],
"module": "commonjs",
"outDir": "build",
"resolveJsonModule": true,
"strict": true,
"target": "es2019"
},
"include": [
"./**/*.ts"
],
"exclude": [
"./build"
]
}
Does someone know how to fix this? Do I have to use some additional packages or modify my TS configuration?
Please let me know if you need more information!
This looks like an inconsistency (at least) between the Node.js documentation (and runtime) and the #types/node package in DefinitelyTyped. This issue is very similar to this one with URL, which has since been fixed by adding a global declaration in url.d.ts. It may be worth opening an issue (and doing a PR).
As you say, TextEncoder is definitely a global (both the documentation and a quick test tell us that), but #types/node/globals.d.ts doesn't have it. #types/node/utils.d.ts does have it (as you indicated), and even has the comment "The TextEncoder class is also available on the global object." but...it's not (unlike URL, which [now] is).
If the code you're writing is Node.js-specific, I'd just do the import and not worry about it.
But if the code you're writing is supposed to target both Node.js and browsers, that's obviously not an option. One workaround mentioned in the URL issue thread is to add "dom" to your lib config setting, but that means all the DOM stuff is added to your global namespace.
Alternatively, until/unless the types are updated, you can declare the global yourself. In the root of your project:
whatever.d.ts:
import { TextEncoder as _TextEncoder } from "node:util";
declare global {
var TextEncoder: typeof _TextEncoder;
}
You don't get useful popup information when you do new TextEncoder(, but it does work, and you do get useful information for its methods etc.

TSLint: how to ignore JS files?

How can I tell tslint to ignore JS files?
In tsconfig.json I have "allowJs": true because I do want it to compile JS files:
{
"compilerOptions": {
// ...
"allowJs": true,
// ...
},
// ...
}
However I do not want it to lint them.
With this setup, it complains that "No valid rules have been specified for JavaScript files". This is because it tries to lint JS files, but has not been given any rules for doing so.
It has been suggested in another Stack Overflow thread to add the following, so that it has some rules to go by (a bit of a hack really):
"jsRules": {
"no-empty": true
}
But what if I don't want it to check for this rule either? I just don't want it to lint JS files at all.
I've found out how to do this.
To ignore JS files, add the following to your tslint.json (you can, of course, ignore any file type in a similar fashion)
"linterOptions": {
"exclude": [
"**/*.js"
]
}
Note that linterOptions.exclude was introduced in tslint 5.8, so you must have this version or later to use this feature.

Requiring a JavaScript Node.js module in TypeScript (allowJs' is not set)

I have an Angular2 app inside Electron. Now, I would like to use the #pokusew/pcsclite library to use NFC functionality. This library uses native Node.js modules.
When I try to require the library in my component.ts like this:
declare var pcsclite: any;
var pcsclite = require('../../../node_modules/#pokusew/pcsclite/');
I get and error that says:
error TS6143: Module '../..' was resolved to '../../lib/pcsclite.js', but '--allowJs' is not set.
On the other hand, if I try to import the library via a <\script>-Tag in the index.html I get an error that says:
ZoneAwareError Error: Could not locate the bindings file. Tried:...
Finally, if I var pcsclite = require('#pokusew/pcsclite'); in the main.js, then it works, but then I don't have access to it from inside my Angular app.
Add the allowJs option in your tsconfig.json like this:
as fabian lauer said also add outDir option to specify where your compiled files will be:
{
"compilerOptions": {
"outDir": "./built", <--- add this
"allowJs": true, <--- and this
"target": "es5"
},
"include": [
"./src/**/*"
]
}

Need help getting gulp typescript workflow

I've been steadily improving my JS workflow thanks to gulp. Now that I've got the basics of es6 and typescript down, I'd like to add it to my gulp workflow. I am using Visual Studio Code on a Mac and I am using a tsconfig.json rather than gulp-typescript options.
My gulp workflow should look a bit like:
take all ts files from src/ts
compile to es5
combine to a single file
use es6 imports/exports pull functions/objects from one module into another.
The goal is to have a single closure wrapped module (revealing module pattern) and all of my classes/functions inside of that module.
My current setup (relevant lines):
var gulp = require('gulp'),
plumber = require('gulp-plumber'),
ts = require('gulp-typescript'),
tsProject = ts.createProject('tsconfig.json');
gulp.task('ts', function() {
var tsResult = tsProject.src()
.pipe(plumber())
.pipe(ts(tsProject));
return tsResult.js.pipe(gulp.dest('./'));
});
tsconfig:
{
"compilerOptions": {
"target": "es5",
"outFile": "build/js/app.js",
"module": "es6",
"noImplicitAny": true,
"removeComments": true
},
"files": [
"src/ts/file1.ts",
"src/ts/file2.ts"
]
}
I created two simple test ts files:
file1:
import {someFunction} from "ajax";
someFunction("Testing");
file2:
export function someFunction(message: string) {
let test = `Hello World ${message}`;
console.log(test);
}
So first issue: it produces an app.js in the build/js folder, but it is empty unless I comment out the import statement. Am I misunderstanding how es6 imports work or how maybe typescript works?
Second issue: it creates a new directory structure at root that is basically: Users/username/Documents/JS/Project/ts/files. These files are partially transpiled to es5, retaining the export and import keywords.
I really like typescript and es6 and want to add it to my workflow but so far no online resources have helped me get this build working.
UPDATE:
I just realized I was trying to compile directly to ES5 using es6 modules. Since that won't work, would it be easier to compile to es5 with commonjs modules and then use something like browserify to combine the code?
If I change my tsconfig to:
{
"compilerOptions": {
"target": "es5",
"outFile": "build/js/app.js",
"module": "commonjs",
"noImplicitAny": true,
"removeComments": true
},
"files": [
"src/ts/file1.ts",
"src/ts/file2.ts"
]
}
That weird full path directory contains the correctly transpiled code but the app.js file is still blank.
UPDATE 2:
I got the weird added directory problem solved by adding "isolatedModules": false to my compilerOptions. I changed the module to commonjs as I've decided to use browserify to handle combining modules.
I changed the the gulp.dest to build/js and removed outFile. This has gotten me to the point where I get the output in the correct directory but it adds it to a sub-folder. Basically I want it to output: build/js/files but it outputs build/js/src/ts/files.
UPDATE 3:
I just wanted to add something to the above for anyone else who runs into this problem. The reason I had issues with the modules, is because I was using outFile. outFile does not allow external modules and will output an empty file.

Categories