Configure ESLint RuleTester to use Typescript Parser - eslint

I am trying to write some custom ESLint rules for my typescript based project. In my project I am using eslint/typescript for linting.
I have already written a custom eslint plugin which validates a custom rule. Now I want to write the unit tests for that custom rule. My test file looks like this:
/**
* #fileoverview This rule verifies that logic can only depend on other logic
* #author Dayem Siddiqui
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const typescriptParser = require('#typescript-eslint/parser')
var rule = require("../../../lib/rules/logic-dependency"),
RuleTester = require("eslint").RuleTester;
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
typescriptParser.parseForESLint()
var ruleTester = new RuleTester({ parserOptions: {} });
ruleTester.run("logic-dependency", rule, {
valid: [
`class DeleteLogic {
}
class CreateLogic {
constructor(private deleteLogic: DeleteLogic) {}
}`
],
invalid: [
{
code: `class ExternalClient {
}
class UpdateLogic {
constructor(private externalClient: ExternalClient) {}
}`,
errors: [
{
message: "Logic cannot have a dependency on client",
type: "MethodDefinition"
}
]
}
]
});
Right now my tests a failing because by default eslint only understand plain Javascript code. I understand that I need to somehow configure it to use a custom parser that allows it to understand/parse typescript code. However I am unable to find a good example online on how to do that

The RuleTester constructor takes eslint's parser options that allow for configuring the parser itself. So pass it in like this and Bob's your uncle:
const ruleTester = new RuleTester({
parser: '#typescript-eslint/parser',
});
typescript-eslint's own rule tests (e.g. this one) use exactly this.
(Was searching for an answer to this question and kept up ending here, so I posted the solution I found here in the hope it'll be useful.)
eslint 6.0+
Incorporating #Sonata's comment:
Eslint 6.0+ requires an absolute path to the parser (see 6.0 migration guide):
const ruleTester = new RuleTester({
parser: require.resolve('#typescript-eslint/parser'),
});

Use a package such as the typescript-eslint/parser. I can't provide much more than a link in this case. If you need help using it, let me know.

Related

How to create a custom blueprint?

I'm trying to create a customized JHipster blueprint for my organization.
I've started my journey:
Installed Yeoman v4.3.0
Installed Jhipster v7.9.3
Created a directory for my future blueprint mkdir mygenerator && cd mygenerator
Executed the command to create a new blueprint: jhipster generate-blueprint
selected only the sub-generator server
add a cli: Y
Is server generator a side-by-side blueprint: Y
Is server generator a cli command: N
selected the tasks: initializing, prompting and configuring
From this point, I've opened the generated blueprint project with VS Code and noticed a first problem, some jhipster packages can't be resolved:
Unable to resolve path to module 'generator-jhipster/esm/generators/server'
Unable to resolve path to module 'generator-jhipster/esm/priorities'
I also noticed that the generator created for me has a small difference from the existing generators in the JHipster Github, such as jhipster-dotnetcore, generator-jhipster-quarkus, generator-jhipster-nodejs: the returned functions are async while in the cited repos they are regular functions (sync):
get [INITIALIZING_PRIORITY]() {
return {
async initializingTemplateTask() {},
};
}
Does it make any difference in this Jhipster version or there is no problem if I return the same way as jhipster-dotnetcore:
get initializing() {
return {
...super._initializing(),
setupServerConsts() {
this.packagejs = packagejs;
...
I've assumed that this detail is not important and followed with async function and write my prompting function to get some input from the user/developer in order to replace values in the template files :
get [PROMPTING_PRIORITY]() {
return {
...super._prompting(),
async promptingTemplateTask() {
const choices = [
{
name: 'OAuth 2.0 Protocol',
value: 'oauth2',
},
{
name: 'CAS Protocol',
value: 'cas',
},
];
const PROMPTS = {
type: 'list',
name: 'authenticationProtocol',
message: 'Which authentication protocol do you want to use?',
choices,
default: 'oauth2',
};
const done = this.async();
if (choices.length > 0) {
this.prompt(PROMPTS).then(prompt => {
this.authenticationProtocol = this.jhipsterConfig.authenticationProtocol = prompt.authenticationProtocol;
done();
});
} else {
done();
}
},
};
}
<%_ if (authenticationProtocol == 'oauth2') { _%>
security:
enable-csrf: true
oauth2:
client:
clientId: ${this.baseName}
clientSecret: Z3ByZXBmdGVy
accessTokenUri: http://localhost:8443/oauth2.0/accessToken
userAuthorizationUri: http://localhost:8443/oauth2.0/authorize
tokenName: oauth_token
authenticationScheme: query
clientAuthenticationScheme: form
logoutUri: http://localhost:8443/logout
clientSuccessUri: http://localhost:4200/#/login-success
resource:
userInfoUri: http://localhost:8443/oauth2.0/profile
<%_ } _%>
thymeleaf:
mode: HTML
templates/src/test/java/resources/config/application.yml.ejs
All this done, I've followed the next steps:
Run npm link inside the blueprint directory.
Created a new directory for a app example: mkdir appmygenerator && cd appmygenerator
Started a new example app with my blueprint: jhipster --blueprint mygenerator --skip-git --skip-install --skip-user-management --skip-client answering all question.
Here I've got some surprises:
After answering What is the base name of your application? I've got this warning: [DEP0148] DeprecationWarning: Use of deprecated folder mapping "./lib/util/" in the "exports" field module resolution of the package at /...<my-generator-path>/node_modules/yeoman-environment/package.json. Update this package.json to use a subpath pattern like "./lib/util/*"
My prompting function somehow made some questions be repeated, from question Do you want to make it reactive with Spring WebFlux? until Which other technologies would you like to use?.
When my prompt was finally shown, there was a message in front of the last option: CAS Protocol Run-async wrapped function (sync) returned a promise but async() callback must be executed to resolve
I've made some changes to my prompt function: removed the calling of super._prompting() with the hope to solve the item 2, and removed the async in the hope to solve the item 3.
Well ... apparently it was solved. But I get a new error when JHipster (or Yeoman) try process the template:
An error occured while running jhipster:server#writeFiles
ERROR! /home/fabianorodrigo/Downloads/my-blueprint/generators/server/templates/src/test/resources/config/application.yml.ejs:47
45| favicon:
46| enabled: false
>> 47| <%_ if (authenticationProtocol == 'oauth2') { _%>
48| security:
49| enable-csrf: true
50| oauth2:
authenticationProtocol is not defined
How come authenticationProtocol is not defined? I'm stuck here. What I could noticed is that, in all the Jhipster's generators I've cited above, the prompt function sets the properties like "this.[property] = [value]" and the "this.jhipsterConfig.[property] = [value]" and in the templates they are referenced (just the property's name) and it works.
What am I missing? Why even if I set the property "this.authenticationProtocol" in the function prompting it is not seem at the template?
Yeoman (yo/yeoman-generator/yeoman-environment) are not required and should no be a dependency to avoid duplication in the dependency tree, unless you know what you are doing. JHipster customizes them, yeoman-test is required by tests.
Unable to resolve path to module is a bug at eslint-plugin-import
I also noticed that the generator created for me has a small difference from the existing generators in the JHipster Github, such as jhipster-dotnetcore, generator-jhipster-quarkus, generator-jhipster-nodejs. Those blueprints are quite old (blueprint support is changing very fast for v8/esm) and are full server/backend replacements, seems you are trying to add cas support. The use case is quite different.
Does it make any difference in this Jhipster version or there is no problem if I return the same way as jhipster-dotnetcore? Yes, get [INITIALIZING_PRIORITY]() is the new notation, and INITIALIZING_PRIORITY may be >initializing instead of initializing. The explanation is here. JHipster v8 will not support the old notation.
...super._prompting(), is used to ask original prompts, since this is a side-by-side blueprint, prompts will be duplicated.
[DEP0148] DeprecationWarning: Use of deprecated folder mapping "./lib/util/" is a bug in yeoman-environment, and should be fixed in next version.
CAS Protocol Run-async wrapped function (sync) returned a promise but async() callback must be executed to resolve is shown because you are using async function with const done = this.async(); done(); together.
this.async() is a to support async through callbacks before Promises were a js default.
There are a few blueprints that uses new notation and can be used as inspiration: native, ionic, jooq and entity-audit.
I didn't see anything about the writing priority, so it looks like you are overriding an existing template and the original generator will write it. For this reason you should inject you configuration into the original generator.
The end result should be something like:
get [INITIALIZING_PRIORITY]() {
return {
async initializingTemplateTask() {
this.info('this blueprint adds support to cas authentication protocol');
},
};
}
get [PROMPTING_PRIORITY]() {
return {
async promptingTemplateTask() {
await this.prompt({
type: 'list',
name: 'authenticationProtocol',
message: 'Which authentication protocol do you want to use?',
choices: [
{
name: 'OAuth 2.0 Protocol',
value: 'oauth2',
},
{
name: 'CAS Protocol',
value: 'cas',
},
],
default: 'oauth2',
}, this.blueprintStorage); // <- `this.blueprintStorage` tells the prompt function to store the configuration inside `.yo-rc.json` at the blueprint namespace.
},
};
}
get [CONFIGURING_PRIORITY]() {
return {
configuringTemplateTask() {
// Store the default configuration
this.blueprintConfig.authenticationProtocol = this.blueprintConfig.authenticationProtocol || 'oauth2';
},
};
}
get [LOADING_PRIORITY]() {
return {
loadingTemplateTask() {
// Load the stored configuration, the prompt can be skipped so this needs to be in another priority.
this.authenticationProtocol = this.blueprintConfig.authenticationProtocol;
// Inject the configuration into the original generator. If you are writing the template by yourself, this may be not necessary.
this.options.jhipsterContext.authenticationProtocol = this.blueprintConfig.authenticationProtocol;
},
};
}

How to use node-config in typescript with custom config interface?

This is a follow up to How to use node-config in typescript? since I couldn't find a proper answer.
I'm relatively new to TS and I'm trying to convert our configs from json to typescript, while trying to incorporate types on the config.
What we currently have is something like:
// default.json
{
"server": {
"timeout": 10
}
}
// some-code.ts
import config from 'config';
...
const serverTimeout = config.get<number>('server.timeout'); // 10
Here, we are able to get required configs, but we need to specify the type via generics in code. I'm trying to incorporate (relatively) stronger typing.
Dry conversion:
// default.ts
import { MyConfig } from './config-schema.ts'
const conf: MyConfig = {
"server": {
"timeout": 10
}
};
export default conf;
// config-schema.ts
export interface MyConfig {
server: {
timeout: number;
}
}
// some-code.ts
import config from 'config';
console.log(config); // { "server": { "timeout": 10 } }
console.log(config.get<number>('server.timeout')); // 10
console.log((config as any).server.timeout); // 10
console.log(config.server.timeout); // obviously a compile time error.
As you can see, the last form will NOT work and results in compile time ERROR. I somehow want config to conform to the shape of MyConfig so that config.server.timeout is possible with strong type number.
I need to somehow extend the IConfig interface with MyConfig or force a re-typing of config to IConfig & MyConfig.
My attempt:
// config-schema.ts
import config from 'config';
interface MyConfig {
server: {
timeout: number;
}
}
export (config as any as MyConfig);
// some-code.ts
import config from './config-schema';
console.log(config.server.timeout); // 10
This works, but somehow doesn't feel right to me.
Problems
Forced to use any... can this be avoided somehow ?
Need to do something like import config from '../../core/config-schema'; -> looks ugly, path different for different files, need all project collaborators to follow convention... is it possible to keep it as import config from 'config'; and have the magic abstracted out ?
Compile default.ts to default.js with tscat compile time (so that typescript is not a runtime dependency). Have updated env var NODE_CONFIG_DIR accordingly. Currently I do this, but the config object gets created with an extra hierarchy level. i.e. instead of config.server.timeout, it became config.default.server.timeout
Edit: Third question resolved -- see comment

Stub an export from a native ES Module without babel

I'm using AVA + sinon to build my unit test. Since I need ES6 modules and I don't like babel, I'm using mjs files all over my project, including the test files. I use "--experimental-modules" argument to start my project and I use "esm" package in the test. The following is my ava config and the test code.
"ava": {
"require": [
"esm"
],
"babel": false,
"extensions": [
"mjs"
]
},
// test.mjs
import test from 'ava';
import sinon from 'sinon';
import { receiver } from '../src/receiver';
import * as factory from '../src/factory';
test('pipeline get called', async t => {
const stub_factory = sinon.stub(factory, 'backbone_factory');
t.pass();
});
But I get the error message:
TypeError {
message: 'ES Modules cannot be stubbed',
}
How can I stub an ES6 module without babel?
According to John-David Dalton, the creator of the esm package, it is only possible to mutate the namespaces of *.js files - *.mjs files are locked down.
That means Sinon (and all other software) is not able to stub these modules - exactly as the error message points out. There are two ways to fix the issue here:
Just rename the files' extension to .js to make the exports mutable. This is the least invasive, as the mutableNamespace option is on by default for esm. This only applies when you use the esm loader, of course.
Use a dedicated module loader that proxies all the imports and replaces them with one of your liking.
The tech stack agnostic terminology for option 2 is a link seam - essentially replacing Node's default module loader. Usually one could use Quibble, ESMock, proxyquire or rewire, meaning the test above would look something like this when using Proxyquire:
// assuming that `receiver` uses `factory` internally
// comment out the import - we'll use proxyquire
// import * as factory from '../src/factory';
// import { receiver } from '../src/receiver';
const factory = { backbone_factory: sinon.stub() };
const receiver = proxyquire('../src/receiver', { './factory' : factory });
Modifying the proxyquire example to use Quibble or ESMock (both supports ESM natively) should be trivial.
Sinon needs to evolve with the times or be left behind (ESM is becoming defacto now with Node 12) as it is turning out to be a giant pain to use due to its many limitations.
This article provides a workaround (actually 4, but I only found 1 to be acceptable). In my case, I was exporting functions from a module directly and getting this error: ES Modules cannot be stubbed
export function abc() {
}
The solution was to put the functions into a class and export that instead:
export class Utils {
abc() {
}
}
notice that the function keyword is removed in the method syntax.
Happy Coding - hope Sinon makes it in the long run, but it's not looking good given its excessive rigidity.
Sticking with the questions Headline „Stub an export from a native ES Module without babel“ here's my take, using mocha and esmock:
(credits: certainly #oligofren brought me on the right path…)
package.json:
"scripts": {
...
"test": "mocha --loader=esmock",
"devDependencies": {
"esmock": "^2.1.0",
"mocha": "^10.2.0",
TestDad.js (a class)
import { sonBar } from './testSon.js'
export default class TestDad {
constructor() {
console.log(purple('constructing TestDad, calling...'))
sonBar()
}
}
testSon.js (a 'util' library)
export const sonFoo = () => {
console.log(`Original Son 'foo' and here, my brother... `)
sonBar()
}
export const sonBar = () => {
console.log(`Original Son bar`)
}
export default { sonFoo, sonBar }
esmockTest.js
import esmock from 'esmock'
describe.only(autoSuiteName(import.meta.url),
() => {
it('Test 1', async() => {
const TestDad = await esmock('../src/commands/TestDad.js', {
'../src/commands/testSon.js': {
sonBar: () => { console.log('STEPSON Bar') }
}
})
// eslint-disable-next-line no-new
new TestDad()
})
it('Test 2', async() => {
const testSon = await esmock('../src/commands/testSon.js')
testSon.sonBar = () => { console.log('ANOTHER STEPSON Bar') }
testSon.sonFoo() // still original
testSon.sonBar() // different now
})
})
autoSuiteName(import.meta.url)
regarding Test1
working nicely, import bended as desired.
regarding Test1
Bending a single function to do something else is not a problem.
(but then there is not much test value in calling your very own function you just defined, is there?)
Enclosed function calls within the module (i.e. from sonFoo to sonBar) remain what they are, they are indeed a closure, still pointing to the prior function
Btw also tested that: No better results with sinon.callsFake() (would have been surprising if there was…)

Mock $root with vue-test-utils and JEST

My component has the following computed code:
textButton() {
const key = this.$root.feature.name === //...
// ...
},
Right now I'm trying desperately to mock "root" in my test, but I just don't know how. Any tips?
Vue test utils provides you with the ability to inject mocks when you mount (or shallow mount) your component.
const $root = 'some test value or jasmine spy'
let wrapper = shallow(ComponentToTest, {
mocks: { $root }
})
That should then be easily testable. Hope that helps
There are two ways to accomplish this with vue-test-utils.
One way, as mentioned above, is using the mocks mounting option.
const wrapper = shallowMount(Foo, {
mocks: {
$root: {
feature: {
name: "Some Val"
}
}
}
})
But in your case, you probably want to use the computed mouting option, which is a bit cleaner than a deep object in mocks.
const wrapper = shallowMount(Foo, {
computed: {
textButton: () => "Some Value"
}
})
Hopefully this helps!
If you are interested I am compiling a collection of simple guides on how to test Vue components here. It's under development, but feel free to ask make an issue if you need help with other related things to testing Vue components.
Solution from https://github.com/vuejs/vue-test-utils/issues/481#issuecomment-423716430:
You can set $root on the vm directly:
wrapper.vm.$root = { loading: true }
wrapper.vm.$forceUpdate()
Or you can pass in a parent component with the parentComponent mounting option. In VTU, the paren will be the $root:
const Parent = {
data() {
return {
loading: "asdas"
}
}
}
const wrapper = shallowMount(TestComponent, {
parentComponent: Parent
})
You may use a Vue Plugin inside the test to inject the mock data into localVue so that your components can access it.
import {createLocalVue, shallow} from '#vue/test-utils';
const localVue = createLocalVue();
localVue.use((Vue) => {
Vue.prototype.feature = {
name: 'Fake news!'
};
});
let wrapper = shallow(Component, {
localVue
});
I had the same issues a while ago and I came to the conclusion that accessing this.$root may be a sign that you have to further improve the way you communicate with components. Consider using the plugin structure to define globally available properties and methods not only inside the test for example. Mixins might be helpful as well.
https://v2.vuejs.org/v2/guide/plugins.html
https://v2.vuejs.org/v2/api/#mixins

EsLint - Suppress "Do not use 'new' for side effects"

I saw the way to suppress this with jsLint, tried it, it did not work.
I need the 'new' keyword or my script does notwork.
How can I suppress it in .eslintrc?
Many Thanks
Update: Per Jordan's request.
[Please note my app is written in ReactJs]
// 3rd party
const AnimateSlideShow = require('../js-animation');
export default class Animate extends React.Component {
.......
fetchJsAnimation() {
const animation = this.refs.Animation;
new AnimateSlideShow(animation);
}
......
}
Error: Do not use 'new' for side effects no-new
Now, if I satisfy EsLint, my app craps out:
Uncaught (in promise) TypeError: Cannot set property '_handleMouse' of undefined(…)
Here's the documentation for the ESLint rule in question: http://eslint.org/docs/rules/no-new.html
Disallow new For Side Effects (no-new)
The goal of using new with a constructor is typically to create an object of a particular type and store that object in a variable, such as:
var person = new Person();
It's less common to use new and not store the result, such as:
new Person();
In this case, the created object is thrown away because its reference isn't stored anywhere, and in many cases, this means that the constructor should be replaced with a function that doesn't require new to be used.
I pasted that above because I think it's important to understand what the intent of the rule is, and not just how to make it go away.
If you can't find a way to get rid of new, you can suppress this error with the eslint-disable directive:
fetchJsAnimation() {
/* eslint-disable no-new */
const animation = this.refs.Animation;
new AnimateSlideShow(animation);
}
ESLint directives are block-scoped, so it will be suppressed inside this function only. You can also suppress rules on a single line with the eslint-disable-line directive:
new AnimateSlideShow(animation); // eslint-disable-line no-new
// You can disable the check on the next line as well.
// eslint-disable-next-line no-new
new AnimateSlideShow(animation);
If you really need to disable this rule for your entire project, then in your .eslintrc's "rules" section set the value for this rule to 0:
{
// ...
"rules": {
"no-new": 0,
// ...
}
}
You can also make it a warning instead of an error by setting it to 1 (2 is error).
Try to cover your function into an anonim function
(()=>code)();
in your example
fetchJsAnimation() {
const animation = this.refs.Animation;
(()=>new AnimateSlideShow(animation))();
}
Or you can use this pattern for example modern javascript framework eg. vuejs vue
Here is an example
(() => new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
}))();
Extending on sarkiroka answer, here's an ES5 version (essentially an IIFE with a return statement):
(function (Vue) {
'use strict';
return new Vue({
el: '.unity-header-wrapper'
});
}(Vue));
We're avoiding ESLint unused var error, which appears if used this way:
var myApp = new Vue({
el: '.unity-header-wrapper'
});
We're also avoiding using standalone 'new Vue()' instantiation (which prevents side effects error on ESLint)
var myApp = new Vue({
el: '.unity-header-wrapper'
});
You can also add Vue as a global in ESLint config, to avoid undefined global var error, as seen here: Global variables in Javascript and ESLint
// .eslintrc.json
"globals": {
"Vue": true
}
I use a more declarative form using an init() method inside my class. For example:
class Example {
constructor () { ... }
init () { //this method is using for initialize the instance }
}
So, when you initialize that instance:
const example = new Example()
example.init()
And with this you can avoid the "no new" linter and avoid undefined global without linter comments.

Resources